C++声明、定义、复杂声明及typedef专题

更新时间:2023-05-20 13:55:01 阅读量: 实用文档 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

本文对C++的复杂声明,作了深入地道的讲解,对使用typedef化简复杂声时和还原复杂声明也作了深入详细的讲解。

本文作者:黄邦勇帅(原名:黄勇)

本文是学习C++最基础的内容,因此应对其熟练掌握,本文主要介绍了声明与定义的区别,C++中有关变量及数据类型的描述,其中由浅入深的介绍了复杂声明的分析方法,本文内容由浅入深,内容较为全面。

本本文内容完全属于个人见解与参考文现的作者无关,限于水平有限,其中难免有误解之处,望指出更正。声明:禁止抄袭,复印,转载本文,本文作者拥有完全版权。

主要参考文献:

1、C++.Primer.Plus.第五版.中文版[美]Stephen Prata著孙建春韦强译人民邮电出版社2005年5月

2、C++.Primer.Plus.第四版.中文版Stanley B.Lippman、Barbara E.Moo著李师贤等译人民邮电出版社2006年3月

3、C语言:标准与实现volume 1 作者不详,前言是“姚新颜”写的可能就是作者,出版社不详,2004年8月

4、《程序设计语言C》中华人民共和国国家标准GB/T 15272-94 1994年12月7日发布出版社不详

5、《C++程序设计原理与实践》[美]Bjarne Stroustrup著王刚刘晓光吴英李涛译机械工业出版社2010年6月

6、《C++程序设计语言》特别版[美]Bjarne Stroustrup著裘宗燕译机械工业出版社2010年3月

7、《C和指针》第二版[美] Kenneth A.Reek著徐波译人民邮电出版社出版日期不详

8、《C陷阱与缺陷》Andrew Koenig 著高巍译王昕审校人民邮电出版社出版日期不详

9、《C专家编程》作者、出版社、出版日期不详

10、《C语言核心技术》Peter Prinz εTony Crawford著O’Reilly Taiwan公司译机械工业出版社2007年8月

11、《ANSI C标准详解》作者、出版社、出版日期不详

第3部分声明、定义、复杂声明及typedef专题(共2章

(第2版2014-5-14)共10页)

第1章声明与定义

关键概念

1、对象:指的是某种类型所占据的一片连续的内存单元,注意:对象不仅仅指的是一片连续的内存单元,而且这片内存区域已经指定了某种类型。

2、标识符:标识符就是一个名字,使用标识符主要是要与C++中的关键字相区别,本文所讲的名字和标识符都是指的标识符。具体标识识的规则请参阅《C++整型、字符型、浮点型专题》。

一、变量、类型简单理解请参考《C++整型、字符型、浮点型专题》相关内容。

二、变量、对象、实体深度理解

1、注意:本文中的对象与类的对象是两个概念,应区别对待。

2、对象:指的是某种类型所占据的一片连续的内存单元,注意:对象不仅仅指的是一片连续的内存单元,而且这片内存区域已经指定了某种类型。

3、变量:变量其实就是命名后的对象,也就是说变量是为指定类型的连续的内存单元(即对象)取的一个名字。一块连续的内存单元,若我们使用内存的地址编号来访问这块内存单元,这样的程序很难让人理解,因此就有必要为一块保存特定类型的连续的内存单元(即对象)取一个名字,这个名字就是我们所说的变量。

4、实体:本文中所说的实体就是变量的另一个名字,一般情况下变量指的是像整型,浮点型这些类型的对象,按变量的概念,变量还可以是函数名,指针名,数组名等,为了描述方便,有时会使用实体一词。

5、从以上概念可以看出,变量、对象和实体三者没有什么本质的区别。

三、类型深度理解

1、类型与内存

内存中的比特值的含义完全决定于这块内存单元所表示的类型,保存在计算机中的值是一些二进制比特,这些二进制比特对于计算机来讲,它并不知道代表什么意义,只有当我们决定如何解释这些比特时才有意义,比如65或字符’a’在内存中的比特值是相同的,若将比特值解释为int型,则他是一个十进制数,若解释为char型,则是字符a,因此在内存单元中的数据应具有一个类型,当类型确定后就能对其中的数据作出正确的解释了。

2、类型的作用

1)、类型决定了可以将什么数据赋给对象(比如整数3可以赋给int型,”dd”可以赋给string型等)

本文对C++的复杂声明,作了深入地道的讲解,对使用typedef化简复杂声时和还原复杂声明也作了深入详细的讲解。

2)、类型决定了可以对该对象进行什么样的操作(比如可以对int型的变量或常量进行加、减、乘、除等操作,不能进字符串变量或常量进行加、减、乘、除等操作)。

3)、类型还决定了对象的内存大小、布局和取值范围

3、每个名字(或变量,变量就是命名后的对象,因此一个变量就是一个名字)、表达式都应有一个类型,这个类型决定了可以对这个名字进行什么样的操作,因此类型决定了这个名字或表达式的使用方式。

4、不能对变量赋予一个类型错误的值。

5、编译器会记录每个变量的类型,并确认对它进行的操作是否与类型相一致。

6、下面介绍一些常见的类型

1)、对象类型:指的是可以根据类型确定内存单元大小的类型,这些类型包括整型、浮点型、字符型、布尔型等,注意,函数类型和不完全类型都是无法确定内存单元大小的,因此都不是对象类型

2)、函数类型:是用于描术函数的,函数类型由其反回值类型,形参数目和形参类型表征,函数一般被称为“反回结果为某类型的函数”。

3)、不完整类型:指的是大小(或成员)不确定的类型,元素数不确定的数组,成员没有定义的结构与联合类型,void类型都是不完整类型,而且void永远都是不完整类型。比如int a[]; struct b;等都是不完整类型,不完整类型可以在其他位置使用完整类型说明使该类型成为完整类型。比如对于struct b;可以在以其他位置使用struct b{...};将其说明为完整类型。

4)、算术类型:包括所有整型与浮点类型,即包括所有整数类型(short, int, long等),字符型、布尔型、枚举型及所有浮点型。

5)、标量类型:是不可再分为其他类型的类型,包括算术类型与指针类型两类,也可认为标量类型是除聚集类型之外的所有类型。

6)、聚集(合)类型:是由标量类型、聚集类型聚合而成的,数组、结构、类类型都属于聚集类型。注意:类中的成员不一定是聚合类型,比如class A{public: intb;}; A ma; 则ma.b的类型不是聚合类型而是int型,因为b是对象ma的成员,他的类型是int型的,不是聚合类型。

四、声明与定义

1、声明是一条语句,声明为对象起了一个名字,同时为名字确定了一个类型。

2、声明的作用:

1)、C++使用声明语句来告诉编译器一个对象的名字,比如int x;表示x是一块类型为int的内存区域的名字。

2)、C++使用声明语句告诉编译器一个对象是什么类型。比如语句int x;就能告诉编译器变量x的类型为int型。一

个命名的对象必须有一个类型,有了具体的类型我们才能确定对对象作出什么样的操作,对象能够接收什么样的值等,C++使用声明语句来实现此目的。

3、定义:定义是一个声明,定义对声明的实体给出了一个完整的描术,也就是说一个定义明确的指明了一个名字代表的是什么。特别地变量的定义会为其分配内存空间。

五、声明与定义的区别及注意事项

1、C++中的名字(标识符)必须先声明后再使用,也就是说在使用前必须先确定它的类型,以通知编译器这个名字所引用的是什么类型的实体。

2、对同一个名字只能定义一次,因为定义为命名对象(比如变量,函数名等)分配了内存,同一名字的对象只能分配一个内存位置,所以只能定义一次。

3、对同一个名字的声明可以有任意多次。

4、对同一个名字的所有声明必须具有相同的类型。

5、区别声明与定义的方法:就是在程序中对同一个标识符声明多次,若语句不出错,则是声明,若出错,则是定义,比如extern int a; extern int a;程序不会出错,因此是声明,而int a; int a;则会出错,因此是定义。注:变量的声明与定义详见后文。

5、任何进行了初始化操作的语句都是定义。

6、每个定义都是一个声明,但声明未必是定义。

7、非定义的声明仅仅告诉编译器程序中有这么一个具有指定类型的名字,因此声明不会为对象分配内存(比如为变量分配内存,为函数指定函数体)。

8、对于变量来说,其声明只说明了类型,而定义则会为该变量分配存储空间。

9、对于函数来说,声明也提供了类型(即参数类型和反回类型),而定义才会提供函数体(即{}括起来的部分)。

10、注意:函数体是被作为程序的一部分而被保存在内存中的,因此函数和变量的定义都消耗了内存,而声明则没有。

11、注意:在声明时,在名字后带一对空的小括号程序会认为这是一个函数,而不是对类调用的默认构造函数(详见后

文),比如hy g(); 其中hy是一个类类型(即class hy{...}),语句hy g();只是表明是对一个函数的声明,而不会是使用默认构造函数创建一个类类型的对象g。

六、声明的语法形式

声明的形式之一:[ [存储类区分符] | [类型限定词] ] <类型区分符> 声明符[, 声明符[, 声明符[, ....]];

本文对C++的复杂声明,作了深入地道的讲解,对使用typedef化简复杂声时和还原复杂声明也作了深入详细的讲解。

声明的形式之二:声明区分符声明符[, 声明符[, 声明符[, ....]];

比如:int a, b; 就表示声明了两个int型的变量a和b,其中int是类型区分符,a和b都是声明符

说明:

1、方括号”[]”中的内容表示可选项,以短竖线”|”隔开的内容可以只选一项,尖括号<>中的内容是必选项。

2、存储类区分符有:extern , static, auto, register, typedef。其中extern表示外部的,static表示静态的,auto表示自动,register表示寄存器,存储类区分符请参阅函数与作用域部分内容,本文不深入讨论。

3、注意:typedef是声明语法中存储类区分符的一种,因此typedef语句是一个简单的声明,可以按照声明的原理来解释typedef。

3、类型限定词有2个:const, volatile。其中const一般被理解为只读,而volatile则是易变。

4、类型区分符有:基本类型(如int, float, double, unsigned long int等);void类型;枚举、结构或联合类型;用户自定义类型;注意,声明时的类型区分符是不可省略的

6、声明区分符由:存储类区分符、类型限定词、类型区分符组成,其中在C++中类型区分符是不可省略的;比如extern const int *p; 其中extern const int 共同组成声明区分符,其中extern是存储类区分符,const是类型限定词,int是类型区分符,*p是声明符。再如int p; 这里的声明区分符只有一个即类型区分符int,后面的p是声明符。

5、声明符:

1)、声明符可以有多个,多个声明符之间使用逗号相隔开。这样就可以声明多个变量了。

2)、有5种不同的声明符,即标识符,函数声明符(),数组声明符[],指针声明符*,引用声明符&;这5种声明符

中可以相互进行一些嵌套,嵌套之后仍是一个声明符且是一个整体,而不是两个及以上单独的声明符,比如*和标识符结合是一个声明符,表明该标识符是指针,再如*a[2]也是一个声明符,表明标识符是一个数组,这个数组中存储的是一个指针变量,*a[2]并不是单独的三个声明符(即指针声明符,标识符,数组声明符),根据声明符的不同,我们可以声明不同的对象。

4)、注意:声明符中也可以出现类型限定词,声明符中的类型限定词一般出现在指针声明符之后或函数声明符的小

括号内(即用于限定形参)。比如int *const p; 这里的类型限定词应属于声明符的内容,而不是类型区分符,即这个声明的声明符是*const p; 声明区分符是int。

5)、若声明符就是一个标识符,则标识符会被声明为一个变量。

6)、函数声明符():若标识符后跟一对圆括号的情形,圆括号内可能有形参表,则这时标识符将被声明为一个函数,

比如int f(float);就表示标识符f是一个反回类型为int带有一个float形参的函数。

7)、数组声明符[]:若标识符后跟一对方括号,方括号内是任选的常量表达式,则这时标识符将被声明为一个数组,

比如int a[11]; 表示标识符a是一个int型数组。

8)、指针声明符*:若在标识符的前面有一个星号”*”,在星号和标识符之间可能会有类型限定词,则这时标识符将被声明为一个指针,比如int *p; 表示标识符p是一个int型指针;再如int *const p; float *p; 等。

9)、引用声明符&:若在标识符的前面有一个符号”&”,则这个标识符将被声明为一个引用,比如int &b;表示标识符b被声明一个类型为int的引用。

7、理解声明符和声明区分符的作用:在声明多个变量时,声明符就能显现出其作用了,声明符说明了所声明的标识符是一个普通变量、函数、指针、数组还是引用,在声明多个标识符时,每个标识符是以逗号分隔的,但每个声明的形式都应是“声明区分符+声明符”的形式,因此在使用逗号声明多个标识符时,应把声明区分符和声明符分别找出来,然后才能判断出声明的标识符究竟是普通变量还是指针、数组、函数、引用。比如const int *const f1(), *const p1, p2, *f2(); 这个声明看起来很复杂,其实将声明区分符和声明符区别开来之后就会很简单,这里声明了4个标识符f1, p1, p2, f2 其中const int是声明区分符,*const f1是一个声明符,*const p1是一个声明符,p2是一个声明符,*f2()又是一个声明符,因此这里声明了一个函数f1,该函数反回一个指针,这个指针是常量,指针指向的是const int类型的变量;同理指针p1是个常量,指向的是const int类型的变量,同理p2是const int类型的变量,而不是const int * const类型。同理f2是一个函数,这个函数反回的是一个指针,这个指针指向const int类型的变量,其实p1, p2和f2分别相当于const int *const p1; const int p2;和const int *f(); 因此理解声明符和声明区分符是相当重要的。

七、声明的规则和限制

1、注意:声明语句后面有一个分号,分号表示此声明语句到此结束。C++中的语句详见后文

2、在一个声明中最多只能有一个存储类区分符,即extern , static, auto, register,typedef只能有其中一个。

3、一个标识符必须指定一个类型,且只能指定一种类型,也就是说不能让一个变量即是int型又是float型。注意:C语言中可以不为标识符指定类型,比如const x;将是一个声明(缺省为int型),但C++必须为标识符指定类型,C++没有缺省的类型。

4、可以使用多个相同或不同的类型限定词(即const和volatile),顺序不关紧要,编译器会忽略相同的多余的限定词。

5、存储类区分符,类型限定词,类型区分符必须在标识符的前面,三者之间的顺序不关紧要。比如int const a与const

int a是相同的,同理int const *p与const int *p是相同的,但要注意int * const p与const int *p是不同的,具体内容参看指针章节。

6、若函数是类中的一个成员时,类型限定词const和volatile可以出现在函数名的后面,注意:类的成员变量不能将

const放在变量名的后面,类的内容具体参见相关内容。

7、声明符中的()、[]应放在标识符的后面,*、&、*const应放在标识符的前面,且()与[]优先级相同,()和[]比*和&优

本文对C++的复杂声明,作了深入地道的讲解,对使用typedef化简复杂声时和还原复杂声明也作了深入详细的讲解。

先级更高。

8、注意:*可以放在类型限定词const和volatile的前面,但不能放在类型区分符(如int, float等)和存储类区分符(如auto, extern等)的前面。

9、函数的反回值不能是一个函数,但反回值可以是一个函数指针,比如int f()();是非法的,int (*f())()是正确的。

10、函数的反回值不能是一个数组,但反回值可以是一个指向数组的指针,比如int f()[]是错误的,int (*f())[];是正确的。

11、数组里面的元素不能有函数,但数组里面可以有函数指针,即int f[]()是不正确的,int (*f[])();是正确的。

12、在数组里面可以有其他数组,即我们经常见到的二维数组int f[][];是正确的。

13、使用声明语法可以产生很复杂的声明,比如int *const volatile *(*f)();等,复杂声明的类型的判断方法,本文后面会

做详细介绍。

14、关于指针,函数,数组的内容将在相应章节详细介绍,在理解声明时不必知道函数,指数,数组是什么,我们只

需知道他们的形式即可,即在标识符后有方括号是数组,比如a[],在标识符后有圆括号是函数,比如f(),在标识符前有星号是指针,比如*p等。

八、变量的声明、定义及初始化、赋值

1、注意:此节所讲的变量不包括函数,指数,数组,关于函数、指针、数组的声明与定义详见相关章节。

2、对于变量来说未使用extern关键字的声明是一个定义,因此会为变量分配内存空间,

3、初始化:是指在定义变量时给出变量的初始值,比如int a=1;表示将变量a的值初始化为1,这时变量a拥有值1。

4、两种初始化变量的形式:即复制初始化和直接初始化。使用这两种初始化方法的原因是C++在创建类的对象时会有这两种初始化方法,而且他们是有一定差别的,但对于内置类型变量来讲不会有什么差别。

5、复制初始化:就是在定义时使用赋值运算符(即”=”)进行的初始化,比如int a=1; float b=2.0;就是复制初始化。

6、复制初始化有两种形式:第一种使用表达式,比如int a=1; int b=2+3;等,第二种使用初始化列表(即使用大括号)的形式,初始化列表就是将各个表达式放在大括号中,表达式之间使用逗号隔开,这种形式一般用于初始化数组;示例int a={1}; int b={2+3}; int c[2]={1,2+3};有关数组的初始化请参看数组相关部分内容。

7、注意:使用初始化列表初始化时,大括号中的表达式在C++标准中是以逗号结尾的,当然也可省略,因此int c[]={1,3,};是正确的初始化语法。

8、直接初始化:是指在定义时将值放在变量名后的一对小括号中,比如int a(1); 将把变量a的值初始化为1,

9、注意:在直接初始化时,变量名后的括号不能为空值,若为空值则表示的是声明一个反回指定类型的无参函数,比

如int a(); 表示声明的是一个反回int型的没有形参的函数,而不是在定义一个变量a。

10、复制初始化与赋值的区别:复制初始化使用赋值运算符”=”对变量进行初始化,会让人以为初始化时就是在赋值,

其实初始化与赋值是有区别的,它们的区别在于,初始化是在创建变量的时候给他一个初始值,而赋值则是发生在变量已经创建之后进行的擦除旧值写入新值的操作。比如int a; a=2; 就是赋值,语句int a;定义了一个变量,并为变量分配了存储空间,这时变量已经拥有一个值,若为局部变量(有关局部变量见相关内容)则这个值是随机的,然后a=2; 将变量a的随机值擦除,并将新值2写入到变量a中;而int a=2; 则是在创建变量a时(这时变量a还没有值)就将变量a的值初始化为2了。

11、若是在函数体外定义的或使用static定义的内置类型变量都会自动初始化为0,若是在函数体内定义的内置类型变

量则不会对其进行初始化,这时变量虽没有初始化,但不代表变量就没有值,编译器一般都会为变量产生一个随机值,若在程序中使用了这种变量将会导致一个难以发现的错误,因此建义对内类型变量最好都要初始化,然后再使用。

12、变量的非定义声明:若使用extern来声明变量,同时没有初始化变量,则是对变量的一个非定义的声明,extern

表明此变量在程序中的其他地方有定义或者在其他文件中有定义;比如extern int i; 表示声明一个变量名为i类型为int的变量,此变量在其他地方有定义。

13、非定义声明变量的条件:1、使用extern关键字;2、变量没有初始化。比如extern int x;表示声明一个变量x,而

int y;因为没有使用extern关键字,因此语句不是一个非定义的声明,同理extern int z=1; 这里对变量进行了初始化,因此同样不是一个非定义的声明。

14、注意:使用extern关键字声明变量时不能在函数内对其进行初始化,比如void f(){extern int a=1;}将会得到一个错

误。

15、非定义声明变量与定义的区别:使用extern对变量进行的是一个非定义的声明,因为变量没有定义,因此可以对

变量进行多次声明,而定义,则只能进行一次。

16、声明(或定义)和初始化多个变量:要声明多个变量只需要在每个变量名之间使用逗号隔开即可,比如int a,b,c; 再

如extern int a,b,c;要对声明的多个变量进行初始化,只需在要初始化的变量使用初始化的方法即可,比如int a, b=2, c; 再如int a=1,b=2,c=3;等。

17、有关extern关键字的具体使用,请参阅《函数与作用域专题》章节。

九、使用类型限定词(const和valatile关键字)声明或定义变量

1、可以使用多个相同或不同的类型限定词(即const和volatile),顺序不关紧要,编译器会忽略相同的多余的限定词。

2、若变量使用const限定词,则该变量就是常量或是只读的,在定义该变量后,就无法修改变量的值。

3、volatile限定词,表示变量可能会被其他程序或事件所修改。因为其他程序或事件可能修改其值,因此volatile关键字还告诉编译器,在每次使用该变量时,都要进行重新读取。

本文对C++的复杂声明,作了深入地道的讲解,对使用typedef化简复杂声时和还原复杂声明也作了深入详细的讲解。

4、编译器可将非volatile类型的const对象放在只读存储区,若从不使用它,则编译器有可能不会为该变量分配内存。

5、若变量同时使用const和volatile限定,则此变量无法被程序本身所修改,但可以被其他程序或硬件修改。

6、使用const声明常变量时,const关键字与类型区分符的顺序无关紧要,比如int const a=1;与const int a=1;是等价的。

7、使用const声明的变量必须进行初始化,因为使用const后是一个常量,其值无法在以后使用赋值运算符进行修改,因此在声明时必须进行初始化。

8、注意:若const的左右都没有类型区分符,且左边也没有*指针运算符时,这种声明是错误的,比如int (const p)=0;是错误的,再如int (const p[3]); int *(const p)=0; int *(const p[3])={0};等都是错误的

9、volatile限定词很少被使用,因此不重点介绍。

示例:声明的规则和限制、变量的声明和定义、复杂声明

#include<iostream>

usingnamespacestd;

//a1=2;

inta2;

//int a2;

externinta3;

externinta3;

//extern long int a3;

externinta5=2;

//extern auto int a6;

//const a7; //错误,标识符必须先声明后使用。//未使用extern的变量声明同时是一个定义,因此此语句会为变量分配内存空间。//错误,同一变量只能定义一次。//这是对变量的非定义声明,使用extern关键定,而且未初始化。//正确,可以对同一个标识符声明多次。//错误,对同一标识符的多次声明必须要有相同的类型。//任何进行了初始化的语句都是一个定义。//错误,在声明时只能指定一个存储类区分符,即auto, extern ,static, register之中的一个。//错误,C++中必须为标识符指定类型区分符(比如int, float等),这种语句在C语言中是正常的,

因为C支持默认的int型。

constconstvolatileinta8=8; //正确,声明中可以使用多个相同或不同的类型限定词,对于多于的相同类型限定词编译器会忽略。

若变量同时使用const和volatile限定,则此变量无法被程序本身所修改,但可以被其他程序或硬

件修改。注意:const常量必须初始化。

//int a9 auto; //错误,存储类区分符应在标识符的前面。

//int a10 const; //错误,类型限定词应出现在标识符的前面,但在类中声明的成员函数时除外。

classA{voidf1() const;}; //在类中声明的成员函数可以将类型限定词放在函数名的后面,注意:只有函数才是正确的,对于

变量将会出错。

//int []a11; //错误,()、[]运算符,在声明时必须放在标识符的后面,这种语句在java中是允许的。

//int a12*; //错误,声明时*、&、*const必须放在标识符的前面。

//int *static a13; //错误,*不能放在类型区分符(如int, float等)和存储类区分符(如auto, extern等)的前面int*consta14=0; //正确,*可以放在类型限定词的前面,这时类型限定词限定的是左边的*(即指针),

//int f2()(); //错误,函数不能反回函数。

int(*f3())(); //正确,函数的反回值可以是一个指向函数的指针,函数f3反回一个指针,这个指针指向一个函数。//int f4()[]; //错误,函数的反回值不能是一个数组。

int(*f5())[]; //正确,函数的反回值可以是一个指向数组的指针。

//int a15[](); //错误,数组中的元素不能是函数。

int(*a16[])(); //正确,数组中的元素可以是一个指向函数的指针。

inta17[3][11]; //正确,数组中的元素可以是一个数组,这就是普通的二维数组

inta18=18; //变量的复制初始化语法之一。

inta19={19}; //使用大括号{}对变量进行复制初始化,此方法一般用于声明有多个元素的数组,

inta20[]={1,2,}; //使用大括号{}一般用于初始化有多个元素的数组,每个值之间使用逗号隔开,且以逗号结束,当

然也可以省略最后的那个逗号。

inta21(21); //变量的直接初始化方法,就是在变量名后紧跟小括号并在其中指定初始值。

inta22(); //注意:直接初始化时,变量名后的括号不能为空值,若为空值则表示的是声明一个反回指定类

型的无参函数,因此这里表示的是声明一个反回int类型的无参函数,这里不是在使用直接初始

化方式初始化变量。

inta23; //在函数体外定义的变量会被自动初始化为值0;

voidmain()

{intb1,b2,b3=33,b4,b5=3; //同时声明或初始化多个变量时使用逗号隔开

//extern int a24=3; //错误,在函数内部不能对使用extern声明的变量进行初始化。

externinta23; //extern表明此变量在程序中的其他地方有定义或者在其他文件中有定义,这里a23在函数体外被定义。inta25;// 在函数体内定义的内置类型变量不会对其进行初始化,编译器一般都会为变量产生一个随机值。//复制初始化与赋值区别:初始化是在创建变量的时候给他一个初始值,而赋值则是发生在变量已经创建之后进行的擦除旧值写入新值的操作。

inta26=26; //这是复制初始化,这是在创建变量a26时(这时变量a26还没有值)就将变量a26的值初始化为了

a26=261; //这是赋值,表示将变量a26的旧值擦除,并将新值写入到变量a26中。

//a8=8; //错误,不能对常量a8的值进行修改。

//int *(const p)=0; //错误,const的左右必须有类型区分符或者左边必须有*指针运算符。这里const的左边是小括号”(“}

本文对C++的复杂声明,作了深入地道的讲解,对使用typedef化简复杂声时和还原复杂声明也作了深入详细的讲解。

第2 章复杂声明及typedef的使用

第1节复杂声明

一、运算符的基本规则

1、声明之所以复杂主要是由于*、[]、( )这3种运算符的作用,才使声明变得很复杂,其中*表示指向....的指针,()表示反回类型为....的函数,[]表示....的数组。

2、*是前缀运算符,()和[]是后缀运算符,前缀运算符只能出现在标识符的前面,而后缀运算符则只能出现在标识符的后面,比如int [22]a; int b*; int *[]c; 是错误的,再如int *[] f();错误,后缀运算符[]只能出现在标识符的后面。

二、复杂声明的分析方法

1、优先级规则法:

1)、首先应从未声明的标识符(就是名字)开始分析,应注意区分函数的形参名和被声明的标识符名。

2)、声明中被小括号”()”括起来的内容,优先级最高,若小括号有多层,则最里层的优先级最高,小括号不会与函数运算符的小括号相混淆,这从语句中可以明显的看出来。

3)、后缀运算符比前缀运算符优先级高。

4)、反缀运算符具有相同的优先级,当有多个后缀运算符时,则应按从左到右的顺序进行分析。

5)、当有多个前缀运算符时,应按从右到左的顺序进行分析。

6)、有const和volatile限定词时,若const或volatile后面紧接着的是类型区分符(如int, float等),则限定词将作用于类型区分符,否则,限定词将作用于它左边紧邻的指针运算符(即星号”*”)。这里要注意,若const左边没有指针运算符,则const应是作用于类型区分符,比如int const *p; 与const int *p;是等同的都表示指针指向的是const int类型的变量;同理const int a[1]; 与int const a[1];也是等同的,都表示数组a存储的是数据是const int 类型的。

7)、若声明中有多个标识符(主要是函数的形参名),则从左向右看,最左边的标识符是未声明的标识符。因为函数形参名只能出现在小括号中(区别优先级的小括号除外),而表示函数的小括号()是后缀运算符,后缀运算符只能出现在标识符之后,而未声明的标识符也只能有一个,因此从左向右看第一个出现的标识符一定是未声明的标识符,再后面的标识符只能是函数形参。

8)、注意:若const的左右都没有类型区分符,且左边也没有*指针运算符时,这种声明讲是错误的,比如int (const p)=0;是错误的,再如int (const p[3]); int *(const p)=0; int *(const p[3])={0};等都是错误的。

2、右左法则:

1)、从标识符开始,先向右看,然后再向左看,若遇小括号,则进入小括号进行上述规则,有const和volatile限定词时的规则同优先级规则第6条,若有多个标识符时,则优先级规则的规则7同样适用于右左法则。

2)、右则法则原理:由优先级规则的第1,2,3,4,5条可以明显的看出右左法则的原理。因为后缀优先级高于前缀,且后缀算符只能出现在标识符之后。因此,分析标识符的类型时,总要先向右边看有没有比前缀运算符优先级更高的后缀运算符,然后再看左边。

3)、注意,右则法则不是指的右边看一下左边看一下,而是要先向右看完所有的运算符,直到遇到反小括号或没有运算符时再向左看。比如int **(*p[4][5])[6]; 先向右看,p先与[4]结合,再向右看,再与[5]结合,再向右看,遇到反小括号,向左看,与*结合,跳出小括号,向右看,与[6]结合,右边已经没东西了,再向左看与*结合,再向左看再与*结合,最后再向左看与int结合。

3、复杂声明时标识符究竟是什么(数组,指针,函数或变量?)

1)、若与标识符第一个接合的运算符是一个指针符号*,则该标识符就是指向....的指针,也就是说标识符就是一个指针,只是这个指针指向的内容可能很复杂。比如int (*b)[]; 标识符b先与*接合,因此b是一个指针,这个指针指向的是一个数组,再如int *(*b)();表示b是一个指针,这个指针指向的是一个反回类型为int *的函数。

2)、若与标识符第一个接合的是数组下标运算符[],则该标识符是....的数组,也就是说这个标识符一定是个数组,只是数组里存储的内容可能很复杂。比如int *a[]; 因为[]优先级更高,因此标识符a先与[]接合,因此标识符a是一个数组,这个数组里的内容是int *的指针;

3)、若与标识符第一个接合的是函数符(),则该标识符是反回....的函数,也就是说这个标识符就是一个函数,只是这个函数的反回类型可能很复杂。int (*f())[];表示f是一个函数,这个函数反回的是一个指针,这个指针指向的是一个数组。

4、示例:

本文来源:https://www.bwwdw.com/article/4ue4.html

Top