从变量名开始,先向右,再向左。 遇到括号时,改变阅读方向; 括号内分析完后,跳出括号,或者按照先右后左的顺序,以此类推,直到分析完整个语句。 。 例子:
int (*func)(int *p);
首先找到变量名func,外面有一对括号,左边有一个*,表示func是一个指针; 然后跳出括号,先看右边,再次遇到括号,说明(*func)是一个,所以func是指向该类型函数的指针,即函数指针。 该类型函数的形参类型为int*,返回值类型为int。
int (*func[5])(int *);
func右侧有一个[]运算符,表示func是一个有5个元素的数组; func左边有一个*,表示func的元素是指针(注意,这里的*不是修改func,而是修改func[5],原因是[]运算符的优先级高于*,而func先与[]结合)。 跳出这个括号,看右边,又遇到括号,说明func数组的元素是函数类型的指针。 它指向的函数的形参类型为int*,返回值类型为int。
还可以记住两种模式:
type (*)(....) 函数指针
type (*)[]数组指针
二、两大陷阱
陷阱一:
请记住,与宏不同,您定义的是类型的新别名,而不是简单的字符串替换。 例如:
首先定义:
字符* PSTR;
然后:
int(常量 PSTR,常量 PSTR);
const PSTR 实际上等同于 const char* 吗? 不,它实际上相当于 char* const。
原因是const赋予了整个指针本身恒定性,即形成了常量指针char* const。
简单来说,记住,当const和const一起出现时,它不会是简单的字符串替换。
陷阱二:
从语法上看,它是一个存储类关键字(如auto、、、、等),虽然它并不真正影响对象的存储特性,如:
整数 INT2; //不可行
编译会失败,并提示“More not one class ”。
以上信息来自:/s/k.html 作者:
三、与#的区别
案例一:
一般来说,比#好,特别是有指针的时候。 参见示例:
[cpp] 查看纯文本
char *pStr1;# pStr2 char *;pStr1 s1, s2;pStr2 s3, s4;
上面的变量定义中,s1、s2、s3都定义为char *,而s4定义为char,这不是我们期望的指针变量。 根本原因是#只是简单的字符串替换。 给类型一个新名称。
案例2:
下面的代码,编译器会报错。 你知道哪一个说法是错误的吗?
[cpp] 查看纯文本
char * pStr;char [4] = "abc";const char *p1 = ;const pStr p2 = ;p1++;p2++;
这是 p2++ 错误的。 这个问题再次提醒我们:与#不同,它不是简单的文本替换。 在上面的代码中,const pStr p2 不等于 const char * p2。 const pStr p2 和 const long x 本质上没有区别。 它们都对变量施加只读限制。 不过,这里变量p2的数据类型是我们自己定义的,而不是系统固有的类型。 因此,const pStr p2 的含义是:数据类型为 char * 的变量 p2 被限定为只读,因此 p2++ 是错误的。
第 4 部分材料:使用不良代码抑制
摘要: 声明有助于创建独立于平台的类型,甚至隐藏复杂且难以理解的语法。 无论如何,使用它可以给代码带来意想不到的好处。 通过本文,您可以学习如何避免缺陷并使代码更加健壮。
简而言之,声明为现有类型创建一个新名称。 例如,人们经常用它来编写更美观、可读的代码。 我所说的美观,是指隐藏笨拙的语法结构和特定于平台的数据类型,从而增强可移植性和未来的可维护性。 本文的其余部分将尽力揭示强大的功能以及如何避免一些常见的陷阱。
问:如何创建与平台无关的数据类型,隐藏笨拙且难以理解的语法?
答:使用为现有类型创建同义词。
定义易于记忆的类型名称
最常见的用例是创建易于记住的类型名称来记录程序员的意图。 类型出现在声明的变量名中“''”关键字的右侧。 例如:
typedef int size;
该声明定义了一个名为 size 的 int 同义词。 请注意,没有创建新类型。 它只是为现有类型添加同义词。 您可以在任何需要 int 的上下文中使用 size:
void measure(size * psz); size array[4];size len = file.getlength();std::vector vs;
您还可以屏蔽一致的类型,例如指针和数组。 例如,您不需要像这样重复定义具有 81 个字符元素的数组:
char line[81];char text[81];
每当您想要使用相同类型和大小的数组时,就定义一个数组,如下所示:
typedef char Line[81]; Line text, secondline;getline(text);
同样,指针语法可以隐藏如下:
typedef char * pstr;int mystrcmp(pstr, pstr);
这给我们带来了第一个陷阱。 标准函数 () 有两个类型为“const char *”的参数。 因此,这样声明()可能会误导人们:
int mystrcmp(const pstr, const pstr);
这是错误的,按顺序,'const pstr'被解释为'char * const'(指向char的常量指针),而不是'const char *'(指向常量char的指针)。 这个问题很容易解决:
typedef const char * cpstr; int mystrcmp(cpstr, cpstr); // 现在是正确的
请记住:每当声明指针时,请在最终名称中添加 const,以便指针本身是常量,而不是对象。
代码简化
上面讨论的行为有点像 # 宏,用同义词替换其实际类型。 不同之处在于它是在编译时解释的,从而使编译器能够处理超出预处理器能力的文本替换。 例如:
typedef int (*PF) (const char *, const char *);
此声明引入了 PF 类型作为函数指针的同义词,该函数指针采用 const char * 类型的两个参数和 int 类型的返回值。 如果您想使用以下形式的函数声明,这一点至关重要:
PF Register(PF pf);
()的参数是一个PF类型的回调函数,返回一个签名与之前注册的名字相同的函数的地址。 深吸一口气。 下面我展示一下如果不使用这个语句我们如何实现:
int (*Register (int (*pf)(const char *, const char *))) (const char *, const char *);
很少有程序员理解它的含义,更不用说这种复杂的代码所带来的错误风险了。 显然,在这里使用并不是一种特权,而是一种必然。 怀疑论者可能会问:“好吧,还会有人写这样的代码吗?” 快速浏览一下头文件就会发现 () 函数,这是一个具有相同接口的函数。
和存储类关键字(class)
这个说法是不是有点令人惊讶呢? 就像auto、、、、and一样,它是一个存储类关键字。 这并不意味着它实际上会影响对象的存储特性; 它只是意味着从语句结构来看,该声明看起来像其他类型的变量声明。 这给我们带来了第二个陷阱:
typedef register int FAST_COUNTER; // 错误
编译失败。 问题是声明中不能有多个存储类关键字。 由于该符号已占据存储类关键字的位置,因此不能在声明(或任何其他存储类关键字)中使用它。
促进跨平台发展
还有另一个重要的用途,就是定义与机器无关的类型。 例如,您可以定义一个名为 REAL 的浮点类型,它可以在目标机器上达到最高精度:
typedef long double REAL;
在不支持 long 的机器上,它看起来像这样:
typedef double REAL;
而且,在一台甚至不支持它的机器上,它会看起来像这样:
typedef float REAL;
您可以在每个平台上编译此 REAL 类型应用程序,而无需对源代码进行任何更改。 唯一要改变的就是它自己。 在大多数情况下,即使是很小的更改也可以通过条件编译的魔力完全自动化。 不是吗? 标准库广泛用于创建此类与平台无关的类型: 、 和 是示例。 此外,诸如 std:: 和 std:: 之类的内容隐藏了冗长且难以理解的模板专门化语法,例如 :、> 和 >。
& 结构性问题
(1)最简单的使用
长的 ;
给已知的数据类型 long 一个新名称,调用它。
(2)、与结构结合使用
int iNum;
长的 ;
};
这条语句实际上完成了两个操作:
1)定义新的结构类型
int iNum;
长的 ;
};
分析:叫做“tag”,即“标签”,其实是一个临时名称。 关键字和关键字共同构成了这种结构类型。 无论有没有,这个结构都存在。
我们可以用它来定义变量,但是需要注意的是,用它来定义变量是错误的,因为相加在一起就可以代表一种结构类型。
2) 给这个新结构命名。
;
因此,实际上,我们可以使用定义变量
3)标准做法:
字符*pItem;
*p下一个;
};
*p节点;
3.&#的问题
有两种方法可以定义 pStr 数据类型。 它们之间有什么区别? 哪一个更好?
字符* pStr;
#pStr 字符*;
答案及分析:
一般来说,比#好,特别是有指针的时候。 参见示例:
字符* pStr1;
# pStr2 字符 *
pStr1 s1, s2;
pStr2 s3, s4;
上面的变量定义中,s1、s2、s3都定义为char *,而s4定义为char,这不是我们期望的指针变量。 根本原因是#只是简单的字符串替换。 给类型一个新名称。
上例中的语句必须写为 pStr2 s3, *s4; 从而可以正常执行。
#使用示例:
# f(x) x*x
主要的( )
整数a=6,b=2,c;
c=f(a) / f(b);
(“%d //n”,c);
以下程序的输出是:36。
为此,在很多C语言编程规范中使用#定义时,如果定义中包含表达式,必须使用括号,那么上面的定义应该定义如下:
# f(x) (x*x)
当然,如果使用的话就不存在这样的问题。
4. &# 的另一个例子
下面的代码,编译器会报错。 你知道哪个说法是错误的吗?
字符 *pStr;
字符[4] =“abc”;
const char *p1 = ;
常量 pStr p2 = ;
p1++;
p2++;
答案及分析:
这是 p2++ 错误的。 这个问题再次提醒我们:与#不同,它不是简单的文本替换。 在上面的代码中,const pStr p2 不等于 const char * p2。 const pStr p2 和 const long x 本质上没有区别。 它们都对变量施加只读限制。 不过,这里变量p2的数据类型是我们自己定义的,而不是系统固有的类型。 因此,const pStr p2 的含义是:数据类型为 char * 的变量 p2 被限定为只读,因此 p2++ 是错误的。
#与
1)#宏定义有一个特殊的优点:可以使用#ifdef、#等进行逻辑判断,也可以使用#undef取消定义。
2)它还有一个特别的优点:它符合作用域规则,定义的变量类型的作用域仅限于定义的函数或文件(取决于定义变量的位置),而宏定义则没有此功能。
5.&复杂变量声明
在编程实践中,尤其是在看别人的代码时,我们经常会遇到比较复杂的变量声明,简化有其自身的价值,比如:
下面是三个变量的声明。 我想为每个人定义一个别名。 我该怎么做?
>1:int *(*a[5])(int, char*);
>2:void (*b[10]) (void (*)());
>3. (*)() (*pa)[9];
答案及分析:
为复杂变量创建类型别名很简单。 您只需将传统变量声明表达式中的变量名称替换为类型名称,然后在语句的开头添加关键字即可。
>1:int *(*a[5])(int, char*);
//pFun是我们创建的类型别名
int *(*pFun)(int, char*);
//使用定义的新类型来声明对象,相当于int* (*a[5])(int, char*);
pFun a[5];
>2:void (*b[10]) (void (*)() );
空白 (*)();