声明
一个名字(标识符)想要在C++程序里使用之前必须要先声明。也就是说必须刻画清楚字段的类型,以通知编译器这个字段所引用的是哪个类型的实体。
char ch
这是一个声明,同样也是一个定义。对于ch,他所对应的实体就是适当数量的储存,使其能够被用做一个变量,即这个储存将被分配。
struct myStruct
int myFun(int)
这不是一个定义,因此它需要在其他地方定义它所引用的实体。
在C++中,每一个字段都必须恰好有一个定义,但是声明可以有多个 ,而且必须类型一致。
声明的结构
一个声明由四部分组成:
1.描述符(可选)
2.基础类型
3.声明符
4.初始式(可选)
除了函数和命名空间之外,其他的生命都应该以分号结束。
char* kings[] = {"A","B","C"};
在这个声明中,基础类型是char,声明符是*kings[],初始式是={…}
描述符是一个开始关键字(?),例如virtual,extern,说明了被声明实物的某些非类型的属性。(?)
声明符是由一个字段和若干可选的生命运算符组成。如前缀*const代表常量指针,而后缀()代表函数。后缀的生命运算符比前缀的生命运算符约束更强。因此,*kings[]就是一个指针数组。有时候会需要使用到括号,比如表示一个指向函数的指针。
在一个声明中不能没有类型。(在C++早期版本中会默认为int)
作用域
一个声明将一个字段引入了作用域。也就是说,这个字段只能在程序正文的一个特定的部分内才能使用。
如果一个字段在所有的类,函数和命名空间之外定义,那他就是全局的,他的作用域从声明的那一个点开始,一直延伸到这个声明所在的文件的结束。
一个块里声明的字段可以遮蔽在其外围的块声明的或者全局的字段。也就是说可以在一个块里重新定义一个字段,让它去引用另一个不同的实体。在退出这个块后,字段就恢复了它原来的含义。
如果要引用被遮蔽的全局字段,可以使用作用域解析运算符"::",如:
int x
void func()
{
int x;
x=1;
::x=2;
//尽量避免这种自找麻烦的行为
//被屏蔽的局部字段没有办法去使用
}
初始化
给一个对象提供了初始式,这个初始式就会确定对象的初始值。
如果没有初始式,那么全局的,命名空间的和局部静态的对象(统称静态对象)将被适当初始化为合适的0。
局部对象(自动对象)和在自由存储区(?)里建立的对象(动态对象或者堆对象)则不会用默认值做初始化。
对象和左值和右值
可以分配和使用没有名字的“变量”,而且可以对一个看起来有点奇怪的表达式赋值,如:
*p[a+10]=7;
因此我们需要引入对象这一概念来表示“内存里的什么东西”。一个对象就是内存中一片连续的区域。左值就是所要引用的某个对象的表达式。左值原本是想表示“某个可以放在赋值左边的东西”。但并不是所有的左值都可以放在赋值的左边,它可也以是引用了一个常量。
为了完善左值(lvalue)的概念,在此引出右值的概念(rvalue),简单的说,一个右值就是不是左值的值,比如一个函数的返回值。
要更深入的话,要引入两个特性,这两个特性在一个对象寻址,复制,移动时涉及。
1.是否具有唯一标识(左值):程序所具有的名字,指针,对象的引用。能够确认两个对象是否相同,对象的值是否变化。
2.是否可移动(右值):就是说,能够把对象移动到另一个地方,并且让对象处在一个有效但是未确定的状态。
经过排列组合,可以得到3种可能的组合(不存在既没有唯一标识又不可移动的对象。)
glvalue:广义左值(满足第1,但不满足2)
xvalue:特别的值(1,2均满足)
prvalue:纯右值(不满足1,但满足2)
任何表达式都是左值或者右值,但不会两者皆是。
声明周期
对象的生命周期起于构造函数止于析构函数。对于没有声明构造函数的类型,如int,可以认为它有个默认没有任何行为的构造函数和析构函数。
typedef
如果一个声明以typedef为前缀,它就是为类型声明了一个新的字段,而不是声明了一个指定类型的对象。
typedef char* Pchar;
Pchar p1,p2;
char* p3=p1;//p1,p2都是char* s
可以用来作为原本比较冗长的字段的缩写。
typedef的另一个作用是将对某个类型的直接引用限制到一个地方,如
typedef int int32;
typedef short int16;
无论如何,typedef都是其他类型的同义词,而不是一个独立的类型。