c++的关键字相较于c的关键字的变化,分为新增关键字、新增语义、语义变化、无变化四大类,无变化的我们不提,只看前三部分。
bool类型也叫逻辑类型,是个两值enum,结果为true和false(这两个也是c++的关键字),我们在c程序中也见过bool,实际上c中并没有bool这个关键字,是编程的人自己使用typedef int bool来重命名的,而在c++中原生就支持了该类型,一般占1个字节(与平台有关),用法与c中自定义的相同,因为编译器本来就认识bool,所以在c++中函数重载时,会认为bool是一个不同的类型。
字符类型,一般占用1字节(c和c++并没明确占用1字节),表示字符(ASCI或unicode字符);从c++14开始,char的符号类型取决于平台,如ARM平台是unsigned char,在x64使用的signed char。因为不确定,所以如果我们的程序中对符号有特殊要求,建议指定unsigned和signed。char类型的变量存储的数字在使用cout输出时,会把数字替换成对应的ASCI码输出,而int输出的才是数字。无论什么平台,基本上类型占用的字节满足以下规律。
1byte == char <= short <= int <= long <= long long;
宽字符类型,用于应对超出一个字节的字符的unicode,如汉字。使用和char高度相似,wchar_t具体占用几个字节与平台有关,可能是int也可能是unsigned short,wchar的变量不能使用cin和cout,要使用专用的wcin和wcout,对应的字符串也有一个wstring。
char占几个字节,语言本身是没有规定,主要是由编译器决定的,为了规避这个问题,c++使用char8_t、char16_t、char32_t来进行补充规定。且他们都是无符号类型,stm32单片机中使用的u8其实就是 char8_t。对应的字符串类也就是u8string、u16string、u32string
在c中的逻辑与或非,在c++中还是可以使用,且大部分程序员也都习惯用逻辑符号,在c++中为了降低学习难度,还为其设计了等效的关键字,如and(&&)、or(||)、not(!)等,作为一个有c功底的工程师,其实没太大的意义,所以我们不去展开讲
在c中,我们使用int型变量做函数传参,实际是传值调用,即在函数中对形参进行修改并不会影响到调用函数时传入的变量,他是对赋值进行的拷贝,只有使用指针传入地址才能改变原始变量,然而指针的威力太大,稍有不慎就会引起段错误,所以在c++中新增了一个低配版指针“引用”。并把“&”符号新增了一种语法特性来表达引用,他与取地址符除了长得一样,实际上没有一毛关系。如下面的程序执行完后,c和k的值则会被swp函数改变。
void swp(int &a,int &b); //一个函数声明,传参使用引用方式 int c=6,k=5; //定义两个变量 swp(c,k); //函数调用
还有另外一种引用,就是给一个变量定义一个类似于符号链接一样的东西,如下:
int k; //定义一个变量 int &y = k; //给变量创建一个符号链接
在上式中创建了一个引用y同时使其关联到k(给k创建了一个符号链接y),后面我们对y的所有操作都是对k的操作。引用在定义的时候必须进行初始化,不能拆分来做,引用只能初始化一次,后续不允许初始化。
const用来禁止修饰的目标(引用)不准改变,如下函数,const的作用主要就是用来做函数传参的限制的,这里只是描述不能通过引用来修改它关联的变量,我们仍然可以直接对变量进行修改。
void func(const int &a, const int &b)
sizeof引用时得到的大小不是引用的大小,而是引用关联的目标的大小。在struct中定义引用,只要不实例化struct,可以不给引用赋初值(蒙混过关),这个时候我们去sizeof引用时,会发现引用的大小是一个int型的大小,所以凡是说引用不占空间其实是错误的。
引用在编译器中具体的实现如下:
int &a=b 等效于 int * const a = &b
其实引用,就是一个const的指针,因为const所以不能被修改,而只能在定义的时候赋初值,否则指针就失去了意义,引用本质上就是指针,是传址调用,所以才能实现修改源变量的结果。
在c++ 11版本开始,对枚举的定义和使用进行了修改。当然c中的写法也是支持的,c中的枚举成员是全局外链接,问题在于,enum成员容易和宏定义或其它全局变量重名,且所有成员的类型不可指定,而在c++中对这些问题进行了处理,编译器编译前会把枚举进行命名空间封装,空间名就是enum变量名,且允许定义变量时不带enum关键字。
对enum成员访问,要使用命名空间方式访问,如下例,简化版1,如果使用了class定义enum,再直接使用enum成员编译会报错,不使用class则是c中的enum。
完整版: enum class e_test:unsigned int { mon, thu, wen }; //定义一个枚举类型 简化版1:enum class e_test{ mon, thu, wen} 简化版2:enum e_test{ mon, thu, wen} e_test d1; //定义一个e_test变量通用 d1 == e_test :: mon; //变量赋值,通用,该方法解决重命名,但是宏重名解决不了 //因为宏是预编译时直接替换。
枚举成员本身是数字,当我们想把他当数字使用需要进行强制类型转换。如
cout << (int)e_test::mon << endl
d1=(e_test)((unsigned int)d1+1);
我们的ubuntu的版本不同,使用的g++编译工具默认的版本也不同,如果使用了老的版本来编译新的语法特性,则会报错,所以有时候需要我们指定c++的版本。方法如下:
g++ xxx.cpp -std=c++17 //指定c++17版本来编译xxx.cpp,等号旁边不能有空格。
inline是一种“用于实现的关键字”,不是一种“用于声明的关键字”,所以inline必须与函数定义放在一起,而函数声明则可以没有inline,inline函数如果在多个文件有调用,应放在头文件中,inline是对编译器的一种建议,而不是强制,编译器会自动优化inline。想要编译器不优化,inline不能有递归,且代码长度<10行,内部不应有循环,否则编译器可能会拒绝inline。
定义在类中的实体函数将自动被转换成内联函数,如果类中只包含了声明,函数实体在外面,编译器不会自动为其内联处理,而是根据函数定义时是否添加了inline,示例如下:
class A { int a; void func(int a,int b){return a+b}; }
NULL在c中其实是一个宏定义#define NULL (void *)0,在c++中不允许我们int *p=(void *)0;在c++中把NULL直接定义成了0,int *p=NULL等效于int *p = 0,这种方式c++允许。但是也带来了一个新的问题,当我们有两个同名函数一个传参为int型,一个传参为指针型,当我们传入NULL时,c++编译器就不知道我们到底要调用哪一个类型的函数,NULL为0时是一个int型,而c中的NULL又是一个指针,此时NULL有歧义,所以c++另外发明了一个关键字来明确表示空指针nullptr。
NULL是一个宏定义,而nullptr是一个关键字。
NULL在c++中本质上是数字0,而nullptr本质是一个指针类型。
源码如下,其实就是定义了一个const的类,名称为nullptr,任何情况下调用的时候都返回一个0,所以他还是0,但是这个0是一个指针。
const class nullptr_t { public: template<class T> inline operator T*()const {return 0;} template<class C, class T> inline operator T C::*() const {return 0;} private: void operator&() const; } nullptr={};
nullptr不属于任何一种对象指针,但是可以表示任何类型的空指针,和c中的void *一样,nullptr本来是用来解决传参为int型和char *型的函数重载的问题的,但是如果一个传参是char * 和int * 它还是不能区分开,问题并没有彻底解决,导致很多人觉得这是个没用的玩意儿,仍然还使用if(p != NULL)来判断是否为空指针,该本来也是早期c++使用的方法,nullptr虽然不完美但是还是很有效,if(nullptr != p)或if(p != NULL)在实际编程中都有使用。
断言在c中已经存在,只是不常用,甚至很多人都没听说过,本质是一个宏。他的作用就是去做一个判断,如果判断条件不成立,向 stderr 打印一条出错信息,然后通过调用 abort终止程序运行。使用方法如下:
a = open(file);//打开一个文件 assert(a) //如果打开失败,a为NULL就结束整个程序运行。
assert是运行监测发现错误,而不是编译,在c中编译时错误用#error来输出。c++则引入了static_assert(表达式,“提示字符”),来实现编译时的静态断言,在编译的时候如果static_assert(n)n的结果为假,那么就把提示字符输出。然后结束编译。示例如下:
static_assert(sizeof(int)!=4,"system bit 32"); //检查当前系统位数
其实静态断言static_assert主要用于检查模板参数是否符合预期,一般都是写模板的人对使用模板时传入的参数进行检查,如果不符合要求就编译报错,但是他的这种提示非常模糊,很多时候我们看了不知道提示的意思,所以在c++20中引入了concept来进一步更好的实现模板参数的编译时的类型匹配。
alignof是用来读取对齐单位(字节),与sizeof使用方法很相似,都是关键字后面跟变量,不同的是sizeof是用来测大小,而alignof是用来测对齐单位的。而alignas是用来设置对齐的字节数,alignas只能对编译器默认对齐数向上调整,不能向下调整。如编译器是按照1字节对齐,那么alignas可以调整为2、4…。如果编译器是按照4字节对齐,那么alignas就不能调到2或更小。使用示例如下:
struct alignas(32) s1 { int a; char b; }; alignof(s1); //测试结构体对齐单位。
如果要调到更小就只能使用c中的pragma pack(n),示例如下:
#pragma pack(1) //指定1字节对齐 struct s1 { int a; char b; }; #pragma pack()//结束指定,恢复默认
typeid类似于sizeof,都是一个运算符,在typeinfo头文件中,通过读取该对象的成员name,可以获得一个变量(表达式)、对象的类型。示例如下:
int a; char b; const char* table = typeid(a).name(); //获取a变量的类型 if(typeid(a) == typeid(b)) //判断ab类型是否相同,不需要name
一个表达式的类型分为静态类型(编译时确定的类型)和动态类型(运行时确定类型,如void *),typeid动静态都可以返回,typeid是c++语言的关键字,有编译器和函数库共同支撑。typeid真正实用的地方是在class和继承,并结合指针和应用的使用。
c++的关键字比较多,考虑篇幅问题,将关键字裁剪成上下两部分,本文是上部分,如有兴趣可以继续阅读下部分。