_countof() 宏引申出来的语法知识

本文深入解析了标准库中的_countof宏,介绍了其背后的复杂语法,包括数组引用、数组指针及模板自动推导等概念,并详细阐述了_countof宏的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在stdlib.h里有一个宏_countof,如下:

extern "C++"
{
template <typename _CountofType, size_t _SizeOfArray>
char (*__countof_helper(UNALIGNED _CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray];


#define _countof(_Array) sizeof(*__countof_helper(_Array))
}

这个宏的作用就是得到一个数组元素的大小。

如下使用:

#include <stdlib.h>

int main()

{

      int a[5];

      ......

      int arraySize = _countof(a); //得到数组a的大小 

}

////////////////////////////////////////////////////////////////////////////////////////////////

这个宏使用了一些大家平时都比较少用到的语法。所以理解起来,一下子不是很明白。

所以这里简单说说,如何理解它吧。

为了说明方便,改写如下:
template <typename T, size_t N>
char (* __countof_helper ( T (&_Array)[N] )  )[N];

这是一个函数模板声明,没有定义实现体。
__countof_helper是一个函数(红色部分);
其参数是一个“数组引用”T (&)[N](蓝色部分);
而返回值是一个“数组指针”(指向一个数组的指针) char (*)[N](绿色部分);


首先,需要理解3个C++的语法:


1) 数组引用

T (&)[N] (注意:有个括号)。比如:
int a[5] = {0};
int (&ra)[5] = a; 这里ra就是一个对数组a的引用,其这个数组的大小也是5;

数组的信息包括:数组元素的类型,和数组的大小;声明“数组引用”时,类型和大小都要匹配,否则是错误的。比如:
int a[5] = {0};
int (&ra)[4] = a; //编译错误:error C2440: 'initializing' : cannot convert from 'int [5]' to 'int (&)[4]'

注意:C++里没有“引用数组”这个语法概念。"int &ra[5]"这样的语法是通不过编译的。

2) 数组指针

T (*)[N] (注意:有个括号)。比如:
int a[5];
int (*p)[5] = &a;// 是一个指针,该指针指向“一个有5个元素的数组”;简称为“数组指针”

同样,声明时类型和大小都要匹配,如下是错误的。
int a[4];
int (*p)[5] = &a;//error C2440: '=' : cannot convert from 'int (*)[4]' to 'int (*)[5]'

上面说了,C++不支持“引用数组”;但是“指针数组”是绝对支持的,平常都会经常到的。比如如下:
char * names[] = {
                       "Mike",
                       "John",
                       "Tom" };
names就是一个指针数组,该数组有3个元素,每个元素都是一个char类型指针,指向一个字符串。
从上面的例子可以看出,指针数组里面的指针所指向的那块内存区域里的数据大小可以是不同的。
因为指针本身所带的信息只知道被指的内存是什么数据类型,不知道大小信息。比如:
int a[4]; int b[5];
int * p = 0;
p = a; // ok
p = b; // ok

3) 函数返回“数组指针”的声明语法

T (* Fun( param_list ) )[N];
Fun是一个函数,其返回值类型是 T (*)[N];

为什么不可以如下这么声明?
T (*)[N] Fun( param_list ); 这么声明多好理解啊。但是这么是不行的。
C++语法比较复杂,语言设计者对(),[],*,&,&&这些符号所放的位置有特殊的要求。
<<Thinking in C++>>的第3章有一节“Aside: complicated declarations & definitions”,举了一些复杂声明语法的例子。

第二,需要知道的是:sizeof是在编译期完成的。
sizeof是C/C++语言中的keyword,不是函数。对其参数里的表达式是不会在执行期去执行的,而只是在编译期去推算整个表达式的最后的类型信息。
比如: int a = 1; sizeof(a++); cout << a;   // a还是1. 不会是2;
因为是在编译期完成,编译器只需要类型信息,不需要函数的实现体。所以就可以不用提供定义体。
下面这个例子可能更能说明这个问题。
int Fun(); // only declare it without definition
sizeof( Fun() );  // 这里Fun()函数不会在运行的时候被执行,仅仅是在编译时,编译器需要知道的是:Fun()函数返回值的类型是什么。


第三,需要理解的是“模板自动推导(template deduction)”
模板推导是由编译器在编译期(compile time)完成的,而不是在执行期(run time)完成的。

这个是理解的关键之一。不涉及内存布局分配的问题。编译期间,编译器只关心声明信息(也就是声明式里面所带的类型信息)。模板推导会自动推导模板参数的各种信息(类型,传递过来的数值等)。以下是一个例子:
template<int x, int y>
struct sum_
{
  enum { value = x + y };
};
int sum = sum_<3, 4>::value; // sum的值在编译期间就已经确定下来了,不是在执行期间由CPU运算得到。
这个例子里的模板,仅仅只有数值的传递,没有包含类型。这个可能大家很少见到的。但是模板是支持的。

例子二:
template< typename T, size_t N>
void Fun( T (&)[N] );

这个模板里既有类型T,又有数值N;模板推导时,是根据Fun的实参来推导得到的。比如:
int a[5];
Fun(a); // 编译器经过推导就知道,T=int,N=5。注意这里的N能得到5,是因为Fun的参数声明决定的。这里Fun的参数是:数组引

用。对于“数组引用”,上面说过,编译器能够从实参那里知道数组的类型,又能知道数组的大小。

同时,编译器在进行模板推导时,会对“实参”和“形参”能够匹配的所有情况进行推导,经过所有的尝试都不能匹配,就会报告编译失败信息。比如,对于这个例子,我们将一个指针作为实参传递给它:
int * p = NULL;
Fun( p ); // error C2784: 'void Fun(T (&)[N])' : could not deduce template argument for 'T (&)[N]' from 'int *'
因为一个指针不可能赋值给一个数组引用。所以编译就会出错。


所以对于stdlib.h里的这个_countof(),如果传递一个指针给它,编译就会失败。

同时,需要指出的是,VC6对模板支持的很不好,一些复杂的模板语法就编译不了。根据我的测试,就这里所说的_countof(),在VC6里就编译不了。VC2005/2008就已经支持的很好的。所以这个_countof()是在VC2005中才提供的。在VC6所带的stdlib.h里没有这个_countof()宏的。

【题外话:模板是泛型编程的基础。利用模板自动推导的能力,可以构建出很有技巧性的代码实现。比如:Boost库。更甚者:Loki库<Modern C++ Design>(中文名:C++设计新思维)这本书附带的库。关于模板的语法和自动推导,可以看<C++ Templates the

complete guide>,大陆翻译的中译本编译的不好。侯捷翻译的《C++ Templates全览》比较好,网上有电子的pdf可以找到,但是只

能在电脑上看,不能打印,也不能copy里面的文字。】

第四:注意事项
template <typename _CountofType, size_t _SizeOfArray>
char (*__countof_helper(UNALIGNED _CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray];
#define _countof(_Array) sizeof(*__countof_helper(_Array))

a) #define里的sizeof()的参数是 “*__countof_helper(_Array)”(注意:有个*),而不是“__countof_helper(_Array)”,
如果是“__countof_helper(_Array)”,那么_countof()返回的值永远是一个指针的大小(在32位系统上总是4)。
因为__countof_helper返回的是一个“数组指针”,加上*,就是取得该指针所指向的数组。


sizeof(一个指针)和sizeof(一个数组),是有很大的区别的。看下面的例子:


int a[5];
int *p = a;
int (*pa)[5] = &a;


// p和pa都是指针,但是指针的类型不同:p的类型是int*;pa的类型是int(*)[5]。


sizeof(p)       // = 4;
sizeof(a)       // = 20 = 5*sizeof(int)
sizeof(*p)     // = sizeof(int) = 4;
sizeof(pa)    // = 4;
sizeof(*pa)  // = sizeof(a) = 20;

C/C++对于数组和指针是明确区分其类型的。数组的信息包括(类型+大小),而指针则没有大小信息。
在这里附带要说一下,数组经过函数参数传递后,数组的大小信息就丢失了。比如:
void Fun( int a[5] )
{
   int n = sizeof(a);  // n为4,相对于就是sizeof(int*).因为编译器只传递过来了数组的地址,大小信息没有了。
}


所以,上面Fun的参数声明里面的数组的具体数字是被忽略掉的。其等价声明式可以是:
void Fun( int a[10] );
void Fun( int a[] );
void Fun( int *a );


为了需要把大小信息传递到函数里面去,需要额外加一个参数来表示大小信息,这个大小信息需要调用者来提供,并且有调用者来保证大小信息的正确性。
void Fun( int a[], int size );
或者使用stl里的vector模板类来代替:void Fun( vector<int>& v); 因为vector是一个类,它有size()方法能得到大小信息。

b)__countof_helper函数的类型是char,而不是_CountofType。因为sizeof( * (char(*)[N]) )【注:这么写只是为了示范说明,

在代码里不能这么写,通不过编译的】 刚好等于N,因为sizeof(char)在任何操作系统上都是=1;

c) 这个模板函数只有声明(declaration),没有定义(definition)。没有定义体的原因是:sizeof是在编译期完成的,不需要具体

的定义体出现(上面第二点里说了)。同时,模板推导也是在编译期完成的。所以_countof()整个宏在编译期就完成了。不需要函

数的具体实现体。
如果定义了实现体,_countof依然能工作,但是那些实现体都是不会被用到的,而这些实现体会在程序运行起来之后,加载到内存

里。这样就浪费内存了。
因为__countof_helper()没有定义体,所以使用的时候只能使用_countof宏本身,可不要像下面那样调用__countof_helper()。
int a[5];
__countof_helper(a); // 能编译通过,但是链接时,链接器会告诉你找不到实现体。


d) google chrome浏览器源代码里也有一个类似的宏如下:
template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N];
#define arraysize(array) (sizeof(ArraySizeHelper(array)))

跟_countof的区别是:
1) ArraySizeHelper函数返回的是 char (&)[N],而不是char (*)[N];
2) #define里没有*号。因为sizeof( char (&)[N] ) = N;

为什么stdlib.h里的_countof宏,不用&,而要用*?
在winnt.h里其实也有个同样功能的宏RTL_NUMBER_OF_V2【注:VC6里没有,VC2005才提供的】,其中有

一句注释是这样的:
“We could return a reference instead of a pointer but older compilers do not accept that.”:)
That's it.

winnt.h 里的注释说明的很好,值得看看。摘抄如下:
//
// RtlpNumberOf is a function that takes a reference to an array of N Ts.
//
// typedef T array_of_T[N];
// typedef array_of_T &reference_to_array_of_T;
//
// RtlpNumberOf returns a pointer to an array of N chars.
// We could return a reference instead of a pointer but older compilers do not accept that.
//
// typedef char array_of_char[N];
// typedef array_of_char *pointer_to_array_of_char;
//
// sizeof(array_of_char) == N
// sizeof(*pointer_to_array_of_char) == N
//
// pointer_to_array_of_char RtlpNumberOf(reference_to_array_of_T);
//
// We never even call RtlpNumberOf, we just take the size of dereferencing its return type.
// We do not even implement RtlpNumberOf, we just decare it.
//
// Attempts to pass pointers instead of arrays to this macro result in compile time errors.
// That is the point.
//
extern "C++" // templates cannot be declared to have 'C' linkage
template <typename T, size_t N>
char (*RtlpNumberOf( UNALIGNED T (&)[N] ))[N];

#define RTL_NUMBER_OF_V2(A) (sizeof(*RtlpNumberOf(A)))

P.S. 下面这个链接值得看。其中的解释相当好。
http://blogs.msdn.com/the1/archive/2004/05/07/128242.aspx

附图:一些语法

 

文章摘自:http://blog.youkuaiyun.com/yuanmanzheng/archive/2010/04/11/5472967.aspx

<think>好的,我现在需要处理用户关于如何编写简单安卓计算器应用的请求。首先,根据系统级指令,回答必须使用中文,并且数学公式要用正确的LaTeX格式。不过用户的问题主要涉及安卓开发,可能不太需要数学公式,但还是要留意是否有变量或算法部分需要用到。比如,计算逻辑中的表达式处理可能需要提到,这时候可能需要用行内公式,比如$3+5*2$之类的例子。 接下来,用户明确要求获取教程或示例代码,因此我需要整理相关的步骤和代码示例。根据提供的引用,特别是引用[1]提到用户使用Java开发安卓计算器的后端逻辑,并且重点在界面实现,所以应该围绕这个展开。 首先,回答的结构要清晰,分步骤说明。可能需要包括创建项目、设计界面、实现逻辑等部分。然后,示例代码部分需要用代码块展示,如XML布局和Java代码。同时,要确保代码语法正确,并符合安卓开发的最佳实践。 然后,生成相关问题。需要从用户可能的后续疑问出发,比如如何处理复杂运算、添加历史记录功能、解决按钮点击问题等。这些问题需要与计算器开发相关,并且基于引用内容中的知识点,比如引用[1]提到的界面设计和后端逻辑分离,可以引申出如何优化界面或提升计算性能的问题。 另外,要注意引用标识。在回答中如果提到界面和逻辑分离,需要在末尾加上[^1],因为引用[1]提到了相关内容。其他引用如[2][3][4][5]可能不太相关,但如果有涉及的话也要正确引用。 还需要检查用户提供的引用内容是否有可用资源。引用[2]是关于手电筒应用的示例,引用[5]是Kivy的教程,可能与安卓原生开发无关,所以可能不需要引用这些。主要依赖引用[1]中提到的使用Java开发安卓计算器的经验。 最后,确保回答符合用户可见层的需求,结构清晰,步骤明确,代码示例正确,并且相关问题能引导用户深入学习和解决问题。可能还需要提醒用户关于处理运算顺序(如乘除优先)、小数点输入、异常处理(如除以零)等常见问题,这些可以作为相关问题或回答中的注意事项。</think>以下是开发简单安卓计算器应用的实现步骤和示例代码: ### 一、基础实现步骤 1. **创建Android Studio项目** - 选择Empty Activity模板 - 设置最低API Level为21(兼容95%以上设备) 2. **布局设计(XML)** ```xml <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:id="@+id/input" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="numberDecimal"/> <GridLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:columnCount="4"> <!-- 数字按钮 --> <Button android:text="7" android:onClick="onNumberClick"/> <Button android:text="8" android:onClick="onNumberClick"/> <Button android:text="9" android:onClick="onNumberClick"/> <Button android:text="/" android:onClick="onOperatorClick"/> <!-- 其他按钮省略... --> <Button android:text="=" android:onClick="onCalculate"/> </GridLayout> </LinearLayout> ``` 3. **计算逻辑实现(Java)** ```java public class MainActivity extends AppCompatActivity { private EditText input; private String currentNumber = ""; private double operand = 0; private String pendingOperation = ""; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); input = findViewById(R.id.input); } public void onNumberClick(View view) { Button btn = (Button) view; currentNumber += btn.getText().toString(); input.setText(currentNumber); } public void onOperatorClick(View view) { Button btn = (Button) view; pendingOperation = btn.getText().toString(); operand = Double.parseDouble(currentNumber); currentNumber = ""; } public void onCalculate(View view) { double secondOperand = Double.parseDouble(currentNumber); switch (pendingOperation) { case "+": operand += secondOperand; break; case "-": operand -= secondOperand; break; case "*": operand *= secondOperand; break; case "/": if(secondOperand != 0) operand /= secondOperand; break; } input.setText(String.valueOf(operand)); currentNumber = ""; } } ``` ### 二、关键功能说明 1. **输入处理**:使用`onNumberClick`收集数字输入,支持连续输入(如$123$) 2. **运算逻辑**:采用双操作数模式,存储前一个操作数进行运算 3. **界面更新**:实时显示输入内容和计算结果 ### 三、进阶优化建议 1. **添加小数点处理**: ```java public void onDecimalClick(View view) { if (!currentNumber.contains(".")) { currentNumber += "."; input.setText(currentNumber); } } ``` 2. **错误处理**: ```java try { // 运算代码 } catch (NumberFormatException e) { input.setText("输入错误"); } catch (ArithmeticException e) { input.setText("数学错误"); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值