全局const常量放在只读数据段(.rodata,其中除了全局const常量外,还包括字符串常量,例如,函数printf中的字符串),局部const常量就放在函数堆栈上(这一点和局部静态变量是不一样的),所以局部const常量通过取地址并强制转换的方式可以被修改,而全局const数据如果这样操作的话,代码在运行过程中会出现段访问异常。
宏定义一般定义在头文件中,C++为了让const能够替代普通的宏定义,也允许在头文件中定义const常量。我们知道,如果是普通变量定义在头文件中,编译的时候一定会出现“变量重复定义”这样的错误信息。所以,C++在语法上对全局const常量做了一些强化处理:凡是const修饰的全局常量,等价于前面再加一个static修饰,所以全局const常量的有效范围就是在一个文件内部。(如果其它文件要访问这个const常量,必须自己使用extern对这个常量再声明一次,见《C++ Prime 中文版第四版 P50》)。
另外,有必要把全局const和全局静态变量对比一下,正常情况下全局const的作用域只是单个文件,通过extern,可以将全局const共享给多个文件,但是在内存里只有一份const对象。全局静态变量的作用域也是单个文件,且无法通过extern共享给其它文件,如果全局静态变量定义在头文件中,则每个包含此头文件的源文件都会产生一个作用域在本文件范围内的同名静态全局变量,即编译器将这些同名的静态变量放在不同的内存地址上。
#define MAX(x,y) ((x)*(y)) // 自己的宏定义就必须这样层层加括号
// 得到一个结构体类型中field成员的偏移量
#define OFFSETOF(type, field) ((size_t) &((type *)0)->field)
// 得到一个结构体类型中field成员所占用的字节数
#define FSIZ(type, field) sizeof(((type *)0)->field)
#define Conn(x,y) x##y // “##”表示将参数x和y连接起来
#define ToChar(x) #@x // “#@” 表示给参数x加上单引号
#define ToString(x) #x // “#” 表示给参数x加上双引号
int n = Conn(123,456); // 结果就是n=123456
char* str = Conn("asdf", "adf"); // 结果就是 str = "asdfadf"
char a = ToChar(1); // 结果就是a='1';
char* str = ToString(123132); // 结果就是str="123132";
#define A1(name, type) name_##type##_type
#define A2(name, type) name##_##type##_type
《Effective C++ 第二版 P16》条款1,举例使用内联的模板函数来替换带参数的宏定义(感觉真是不作不死啊,有兴趣的时候再研究吧)。
>>:右移运算符,如果是无符号类型数据,左边空出的位置用0来填补;如果是有符号类型数据,则根据操作系统的规定来补全。有些操作系统是用符号位来填补,有些操作系统默认是按照0来填补。(在Win7 VC2010测试,右移用符号位填补)
<<:左移运算符,右边空出的位置用0来填补,高位左移溢出应该舍弃该高位。而且是全部位参与左移,包括符号位。
~:按位取反
|:或运算符
&:与运算符
^:按位异或运算符
!:逻辑非运算符
||:逻辑或运算符
&&:逻辑与运算符
例如,short int i = 8;short int j = -8;
此时,变量 i 在计算机中的二进制数据表示为:0000 0000 0000 1000。变量 j 在计算机中的表示应该是: 216−8 。因为对于一个位宽为16的数据类型, 216 表示的就是零。这样,计算机在计算 8−8 的时候才能使用普通的加法逻辑电路来统一处理(即:8 - 8 = 8 +(-8)=8+ 2n−8 = 2n =0)。【对于16位数据类型,n>=16】
如果要验证这一点,可以通过下面的代码:
short int i = 8;
short int j = 65536-8; // j=2^16-8
short int k = 65536; // k=2^16
printf("i=%d; j=%d; k=%d \n",i, j, k);
正数的机器码 = 正数的二进制原码
负数的机器码 = 2n – 负数的绝对值(负数的机器码也被称为补码;n大于等于数据类型实际的位宽)
举例:16位数据类型,数值(-8)的机器码 = 216 -8=65536-8=65528 。
short int i = 65528; // 65528 = 2^16 -8
short int j = 131064; // 131064 = 2^17 -8
负数的绝对值 = 2n –负数的机器码(n大于等于数据类型实际的位宽)
short int i = 32767;
short int j = 2;
short int k = i+j;
printf("i+j=%d \n",k); // 输出的结果是“i+j=-32767”
-( 216 - 32769)=-32767
short int k = -32769;
printf("k=%d \n",k);
short int i = 234;
int j = 567;
printf("i*j=%d \n", i*j); // 机器会现将i和j转换为int类型数据后再运算,最后的结果是“i*j=132678”
使用sizeof运算符计算指针变量的长度,在32位系统上,得到的结果是4,在64位系统上,得到的结果是8。所以,通过计算指针的长度可以判断当前是32位系统还是64位系统。其它数据类型的长度如下:
sizeof(char)的长度为:1
sizeof(short)的长度为:2
sizeof(int)的长度为:4
sizeof(long)的长度为:4(Win X86和X64都为4,Linux X86为4,X64为8)
sizeof(float)的长度为:4
sizeof(double)的长度为:8
sizeof(bool)的长度为:1(C++里)
sizeof(BOOL)的长度为:4(windows平台)
数据对齐指的是数据在内存中存放的位置。
自然对齐指的是变量在内存中的起始地址正好是变量长度的整数倍。
为了让CPU读取数据的效率最高,编译器在默认情况下为每个变量都按照自然对齐的方式分配内存地址。
对于标准数据类型:char、short、int、long它们的长度都是确定的,所以对齐的结果也比较明确。
对于非标准数据类型的对齐结果,对齐规则如下:
1)数组 :按照单个数组元素的数据类型对齐,第一个对齐了,后面的数据自然也就对齐了。(简单)
2)联合体 :按其中长度最大的数据成员的数据类型对齐。(简单)
3)结构体: 每个数据成员要分别对齐。第一、各成员变量的偏移地址是sizeof(类型)的整数倍。第二、结构的总大小是所有成员类型中最大sizeof(类型)的整数倍。
struct stMember
{
char m_byte1; // 相对偏移地址是0
double m_double; // 相对偏移地址是8(因为double数据类型的长度是8,这里满足第一条规则)
char m_byte2; // 相对偏移地址是16
};// sizeof(stMember)的结果是24。(这里满足上面的第二条规则)
#pragma pack(4) // 修改当前的对齐参数为4
// 在这里定义的结构体,编译器将参照新的对齐参数n为结构体成员分配内存位置
struct stMember
{
char m_byte1; // 相对偏移地址是0
double m_double; // 相对偏移地址是4(对齐的规则是:按照成员类型的对齐参数(8)和指定对齐参数(4)中较小的一个值对齐)
char m_byte2; // 相对偏移地址是12
};// sizeof(stMember)的结果是16。
#pagman pack() // 取消指定的对齐参数n,恢复缺省对齐参数
__attribute((aligned (n))) // 如果修饰一个结构体,表示结构体的大小是n的整数倍。
__attribute((aligned)) // aligned没有参数,表示“让编译器根据目标机制采用最大最有益的方式对齐",应该就是自然对齐。
__attribute__ ((packed)) // 表示以最小方式对齐,即以一字节为单位对齐。
静态全局对象初始化的时间在main函数执行之前,且不同文件中的静态全局对象初始化顺序无法保证;如果ObjA和ObjB是两个分属于不同源文件的静态全局对象,且ObjB的初始化依赖于ObjA,如何能保证ObjA先完成初始化呢?答案是保证不了。可行的方案是参考设计模式中的单件(singleton)模式,将两个静态对象放入两个函数中(且函数的返回值是静态对象的地址或引用),因为函数内部的静态对象在函数第一次调用的时候完成初始化,这样通过函数调用的顺序,保证对象初始化的顺序。【参考《Effective C++第二版》第47条】
后面在学习C++类继承关系的时候,还有一个概念叫做函数重写(override),重载和重写的区别简单讲就是:重载只要求函数名不变,参数类型可以不一样,重写要求函数名和参数列表都不变。所以说,重载是一个类中多个同名函数处理不同类型的参数,重写是子类对基类函数的重新实现(即多个同名函数处理相同类型的参数,但函数内部算法不一样)。
为了方便编译器识别默认参数,默认参数只能从右至左依次定义。(换句话说:如果某个参数是默认参数,那么它后面的参数必须都是默认参数)
默认参数可以是常量或全局变量,要求在编译的时候是确定的。
一个函数不应该又定义重载函数,又定义默认参数,这会导致编译器无法判断。【《Effective C++ 第二版》第24条 建议:如果两个处理过程需要处理的数据数量不一样,但是数据类型和算法一致,应该用默认参数的方式统一成一个函数实现。如果数据类型或算法不一致就考虑通过函数重载分成多个函数定义。】
可以使用sizeof运算符计算数组的大小。
把数组作为参数传递给函数,实际上就是把数组的地址传递给函数(即函数内部看到的就是一个地址)
const int a = 78; // a是常量
const int* ptr1 = &a; // ptr1是一个指向常量的指针,也叫作常量指针(即常量的指针),但ptr本身是变量。
int const *ptr1 = &a; // 常量指针的另一种写法。
int b = 78;
int* const ptr2 = &b; // ptr2是一个常量,即指针常量,指向b的内存地址。
const int* const ptr2 = &a; // 指向字符常量的指针常量
char Array[]="Hellow"; // 这里的字符串就是在初始化一个数组,所以它不属于字符串常量
sizeof(Array); // 因为系统在字符串"Hellow"的尾部自动添加结束符\0,所以字符串占用数组的实际空间是7字节
strlen(Array); // strlen函数计算的字符串长度不计算结束符null
int a = 100;
int& rNumber = a; // 引用的定义
C++创造引用的目的主要是用于函数的参数传递,而且主要是用于结构体和类这样的数据,因为传递引用可以和传递指针一样高效(不需要在被调函数栈内创建参数副本),并且,引用一旦初始化,就不能指向其它对象,也就防止了因为指针改写而出现的内存丢失问题。另外,如果被定义为引用的参数在函数内部只是被读取,则应该将参数定义为const引用。这样做的优势有三点:
1. 函数定义const形参可以避免传入的参数被无意中修改。
2. 函数定义const形参可以使函数既能够处理const也能够处理非const类型的实参,否则只能处理非const类型的实参(是的,将const对象传给一个函数,如果函数相应的形参不是const类型,则编译失败,此乃常识。)
3. 使用const引用使函数能够正确生成并使用临时变量。
一个类的全部成员变量中如果包含引用,它的赋值运算符重载函数(oprator=)必须由程序员自己定义,编译器拒绝自动生成按位拷贝的默认函数,在用户自定义的赋值运算符函数里不能对引用赋值;同理,如果有const成员变量,也是一样的。
// 计算两个整数相加,返回值是int类型
int function1(int a, int b)
{
return a+b;
}
// 声明函数指针,函数的返回类型是int,*表示后面的func是一个指针变量,*和func的前后必须加上()
int (*func)(int a, int b);
int main()
{
int c = 0;
func = function1; // 初始化函数指针,也可以写成:func = &function1;
c = (*func)(1, 2); // 调用函数指针指向的函数,也可以写成:c = func(1,2);
return 0;
}
关于上面两种函数调用方式,《C++ primer plus 第六版》243页有描述:“观点1认为,func是函数指针,*func才是函数(借鉴了数据指针和数据的概念)。观点2认为:因为func = 函数名,既然能直接用函数名调用函数,就应该直接用func调用函数。最终,C++编译器决定同时支持这两种表示法。”其实,个人认为从语法一致性的角度看,第二种观点更合理,但是从代码可阅读性的角度看,第二种观点容易让人忘记func是一个指针。所以,建议使用观点1。
typedef int (* FUN)(int a, int b); // 定义一个函数指针类型的别名
FUN func1; // 使用简化的类型别名定义函数指针变量
char* pa, pb; // 此时只有pa是指针类型,pb是char类型
typedef char* POINTER_CHAR; //
POINTER_CHAR pa, pb // 此时pa和pb都是指针类型
// 定义与平台无关的数据类型别名
typedef unsigned char U8; // 无符号8位变量类型别名
typedef unsigned short int U16; // 无符号16位变量类型别名
typedef unsigned int U32; // 无符号32位变量类型别名
typedef char* POINTER_CHAR; // const POINTER_CHAR相当于 const char*吗?否,它相当于char* const