11. 关于指针和数组
- 指针就是一种变量,它的值是内存单元的地址。
- 不存在固定的“指针类型”,都是通过基本和复合类型前加“*”声明的。
- 虽然类型名和“*”的组合是一种指针类型,但编译器解释的时候,“*”是跟变量名结合的。所以
int* a,b,c;
这句只有a是指针,b和c都是整型变量。
- 全局指针变量的初值是NULL,非静态局部指针变量初值不可知。
- 不管指针变量是全局的、静态的还是局部的,应当在声明的同时进行初始化,至少用NULL初始化。
- 直接打印指针的值实际是一个整型数值,所以可以直接把一个整型数值赋给指针变量。
- 指针变量可以参加任何算术运算,但大部分没有实际意义。
- 指针和运算符++,是指针指向序列中后一个元素,–是指向序列前一个元素,加一个整数i,表示指针后进i个元素,减一个整数i,表示前进i个元素,两个同类型指针相减,表示计算它们之间的元素个数。
- void*指针不能参与算术运算,只能赋值和sizeof()操作。不能对void*类型的指针使用“*”来提取变量。
- “&”用于指针变量,表示取指针变量的地址。“*”用于指针变量,表示提取指针指向的变量。用“*”时请确保指针指向一个有效的合法的变量。
- 数组是内存中连续字节存放的,下标从0开始,最后一个元素的下标为数组个数减一。
- 使用“[]”引用数组元素的时候,编译器其实是把它转换为指针形式。数组本身就是一个指针,数组名就是数组第一个元素的地址。
- 声明数组是可以明确指定数组个数,编译器会照给定的个数分配内存,也可以不指定个数而直接初始化,编译器会根据初值个数确定数组个数。
- 如果初值个数多于指定个数,会报错;如果初值个数小于指定个数,后面的会自动初始化为0。
- 标准的C++/C不会检查数组访问是否越界,越界的风险需要由你的程序来承担。
- 任何维数的数组都可以看成是比它少一维的数组指针。
- 数组不能从函数的return返回,但数组可以作为函数的参数。当把数组作为参数时,实际是把数组当成指针传递给函数的。
- 对于多维数组向函数传递,必须指出除第一维以外的所有维的长度。
- 数组传递就是地址传递,如果想按值传递整个数组,可以把数组封装到一个struct或class中,就可以按值传递了。
- 数组的动态分配语法为:
char *p = new char[1025];
多维数组动态分配需要按每一维度当成一维数组的方式分配。
- 数组释放的语法是:
delete []p;
而不加[]则只能释放数组的第一个元素的空间。
- 不存在
delete [][]p;
这样的语法。多维数组的释放必须从里到外每一维度释放。
- 字符数组是元素为字符变量的数组,而字符串是以‘\0’为结尾的字符数组。所以字符数组并不一定是字符串。
- 如果用一个字符串来初始化字符数组,则数组的长度要至少比字符串的长度大1,用于保存字符串结尾处的‘\0’。
- 字符数组并不在乎中间或末尾有没有‘\0’,所以如果使用字符串函数处理字符数组,有可能会出错。
- C++/C中类似char*默认表示的是字符串,所以,需要区别字符指针和字符串的差别。
- 字符串拷贝应使用strcpy或strncpy函数,而不能直接赋值。同样也不能用“==”、“>=”、"!="等比较运算符,字符串比较应使用strcmp和strncmp函数。
- 字符串进行复制时,要保证函数结束后目标字符串的结尾有‘\0’。因为有些字符串函数并不会在字符串结尾追加‘\0’。
- 函数指针是指向函数体的指针,值是函数体的首地址,也就是函数名,可以把函数名直接派给一个同类型的函数指针。
- 函数指针经常用于回调函数,函数指针声明方法:
typedef int __cdecl (*FunPrc)(const char *);
FunPrc fp_1 = strlen;
FunPrc fp_2 = puts;
double _cdecl (fp_3)(double) = sqrt;
- 可以直接把函数指针变量当做函数名,填入参数调用函数;也可以将函数指针的反引用作为函数名,然后填入参数调用指针。
- 可以通过函数指针数组实现同类型函数的批量调用。
- 关于类成员函数的指针。inline函数是在运行时展开的,虽然可以取地址,但没有太大意义。virtual成员函数的地址指的是其在vtable中的位置;static成员函数的地址与普通全局函数的地址没有区别;普通成员函数由于调用时时绑定在对象上的,声明方式和取地址方式需要特别处理。
class CTest{
public:
void f(void) {cout << "CTest::f()" << endl;}
};
int main()
{
CTest theObj;
typedef void (CTest::*MemFunPtr)(void);
MemFunPtr mfp = &CTest::f;
(theObj.*mfp)();
return 0;
}
- 引用“&”是C++中新增的概念,这里不是取地址的含义。而是给一个变量取个别名。修改引用的值也就相当于修改原变量的值。
- 引用在创建是必须初始化;而指针在创建是不要求初始化,可以在后面的程序中赋值;
- 不存在NULL引用,阴影必须与合法的存储单元关联;而指针可以指向NULL;
- 引用一旦被初始化指向一个对象,就不能改变为对另一个对象的引用。而指针可以随时更改指向的对象。
- 对对象引用的创建和销毁不会调用类的拷贝构造函数和析构函数;
- 引用主要用途是修饰函数形参和返回值。引用既有指针的效率,又有变量使用的方便性和直观性。
12.关于高级数据类型
- 就C++语言本身来讲,struct和class除了默认成员访问权限不同以外,没有任何区别;struct成员默认访问权限为public,而class成员默认访问权限为private;
- 建议还是使用struct定义简单的数据集合,而用class定义有行为的ADT;
- POD对象初始化可以使用memset()函数,也可以仅指定第一个成员的初值来对对象进行初始化,这样后面的成员将全部自动初始化为0,就像初始化数组那样;
- 构造类型可以嵌套定义,但对于嵌套定义的类型,其对象不一定存在包含关系。同样,存在包含关系的对象不一定是嵌套定义。
- 一个对象不能包含自己,无论是直接的还是间接的都不行,但是对象可以自引用,而且可以交叉引用。可以利用引用关系实现链表、树、队列等数据结构的建立。
- C++和C都支持同类型对象之间的等号直接赋值运算,但是不能比较大小和判断是否相等。
- 位域是以单个的位(bit)为单位来设计struct所需的空间,可以根据数据成员的有效取值范围来仔细规划它们各自所需要的位数。
- C语言位域各成员的类型必须是int、unsigned int、signed int等类型。C++还允许使用char、long等类型。不允许使用指针类型或浮点类型作为位域的成员类型。
- 不要定义超过类型最大位数的位域成员。
- 可以定义非具名的位域成员,其作用相当于占位符,用来间隔两个相邻的位域成员。
- 可以定义长度为0的位域成员,其作用是迫使下一个成员从下一个完整的机器字开始分配空间。
- 不能取位域对象的数据成员的地址,但可以取位域对象的地址。
- 不要把位域成员当做位的数组,不能使用"[]"来访问位域成员的单个位。可以使用位运算符(>>,~,&,|等)或者使用std::bitset<N>
- 设计位域是,最好不要让一个位域成员跨越一个不完整的字节。
- 使用位域会节省内存空间,但却会降低运行速度。
- 结构体类型内的数据成员存储采用“自然对齐”的方式。即每个数据成员变量的起始地址偏移量必然能够整除它的类型字节数。所以一个对象所占内存可能不等于内部所有成员变量字节数的和。
- 遵循自然对齐的复合数据类型的首地址必然能够整除它的基本类型数据成员中字节数最大的那个。
- 可以使用宏代码#pragma来为某些复合类型定义显式指定结构成员的对齐方式,或者使用编译器的命令行参数/Zp为个别文件中定义的复合类型或这个工程中所有复合类型指定对齐方式。一般按1、2、4、8、16字节地址边界对齐。#pragma pack(push,N);
- 指定对齐方式的编译器指令可能是不可移植的。某些环境甚至会不支持用户特别指定对齐方式。具体应查阅编译器的文档。
- 想知道复合类型中每个数据成员的偏移字节数,可以使用offsetof宏。不要使用“类成员的地址”这种方法来计算数据成员的实际偏移,因为这种方法计算的偏移没有把隐含成员考虑在内。
- 设计复合类型的成员数据编排是,应按照字节数从大到小的顺序从前往后依次声明,可以最大程度节省存储空间。
- 联合(union)也是一种构造数据类型,它是一种不同类型数据成员之间共享存储空间的形式。可以实现不同类型数据成员之间的自动类型转换。
- 联合对象在同一时间只能存储一个成员的值,即只有一个数据是活跃的。
- 联合的内存大小取决于其中字节数最多的成员,而不是累加,联合也会进行字长对齐。
- 定义联合变量的时候可以指定初始值,但只能指定一个,而且该初始值必须与联合的第一个成员的类型匹配。
- 可以取联合变量的地址,也可以取一个联合变量的任一个成员的地址,它们总是相同的。
- 同类型联合变量可以赋值,但不能比较大小。
- C++中联合的数据成员可以定义访问说明符,还可以定义成员函数,构造函数和析构函数。
- 枚举(enum)用于定义特定用途的一组符号常量。定义枚举类型时,如果不指定其中标识符的值,则第一个为0,后面的依次大1。如果指定了其中一个标识符的值,它后面的会在它的基础上依次加1。
- 标准C中,枚举类型的内存大小为int类型大小,但是在C++中并非必须,可能更大,也可能更小,具体看指定值的大小,因此也可以指定一个大于int类型最大值的整数作为枚举成员的值。
enum{SIZE = 123456789012}; 在标准C++中是允许的。 - 枚举变量和常量都可以参与整型变量的运算,但注意不要给枚举变量赋予一个不在枚举常量列表中的值。例如,不要对枚举变量使用++,,–,+=,-=等,除非特别为它重载了这些运算符。
- 枚举类型可以是匿名的。匿名枚举类型相当于直接定义的const符号常量,可以作为全局枚举,也可以放在任何类定义或名字空间中。
|