C++ 中常用关键字及其用法

0.1 C++与C的对比

  1. C++有三种编程方式:过程性,面向对象,泛型编程。
  2. C++函数符号由 函数名+参数类型 组成,C只有函数名。所以,C没有函数重载的概念。
  3. C++ 在 C的基础上增加了封装、继承、多态的概念
  4. C++增加了泛型编程
  5. C++增加了异常处理,C没有异常处理
  6. C++增加了bool型
  7. C++允许无名的函数形参(如果这个形参没有被用到的话)
  8. C允许main函数调用自己
  9. C++支持默认参数,C不支持
  10. C语言中,局部变量必须在函数开头定义,不允许类似for(int a = 0; ;;)这种定义方法。
  11. C++增加了引用
  12. C允许变长数组,C++不允许
  13. C中函数原型可选,C++中在调用之前必须声明函数原型
  14. C++增加了STL标准模板库来支持数据结构和算法

 一、常用的关键字极其用法

1.1 const 

主要用法

C++ 的const关键字的作用有很多,几乎无处不在,面试中往往会问“说一说const有哪些用法”。下面是一些常见的const用法的总结:

 

 

除此以外,const的用法还有:

  • const引用可以引用右值,如const int& a = 1; 

注:

  1. const 成员方法本质上是使得this指针是指向const对象的指针,所以在const方法内,
  2. const 成员函数可以被非const和const对象调用,而const对象只能调用const 成员函数。原因得从C++底层找,C++方法调用时,会传一个隐形的this参数(本质上是对象的地址,形参名为this)进去,所有成员方法的第一个参数是this隐形指针。const成员函数的this指针是指向const对象的const指针,当非const对象调用const方法时,实参this指针的类型是非const对象的const指针,赋给const对象的const指针没有问题;但是如果const对象调用非const方法,此时实参this指针是指向const对象的const指针,无法赋给非const对象的const指针,所以无法调用。注意this实参是放在ecx寄存器中,而不是压入栈中,这是this的特殊之处。在类的非成员函数中如果要用到类的成员变量,就可以通过访问ecx寄存器来得到指向对象的this指针,然后再通过this指针加上成员变量的偏移量来找到相应的成员变量。文章来源http://blog.csdn.net/starlee/article/details/2062586/
  3. const 指针、指向const的指针和指向const的const指针,涉及到const的特性“const左效、最左右效”
  4. const 全局变量有内部链接性,即不同的文件可以定义不同的同名const全局变量,使用extern定义可以消除内部链接性,称为类似全局变量,如extern const int a = 10.另一个文件使用extern const int a; 来引用。而且编译器会在编译时,将const变量替换为它的值,类似define那样。

 

const 常量和define 的区别

  1. const常量有数据类型,而宏定义没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换中可能会产生意想不到的错误(边际效应)。
  2. 有些集成化的调试工具可以对const常量进行调试,但是不能对宏定义进行调试。
  3. 在C++程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。
  4. 内存空间的分配上。define进行宏定义的时候,不会分配内存空间,编译时会在main函数里进行替换,只是单纯的替换,不会进行任何检查,比如类型,语句结构等,即宏定义常量只是纯粹的置放关系,如#define null 0;编译器在遇到null时总是用0代替null它没有数据类型.而const定义的常量具有数据类型,定义数据类型的常量便于编译器进行数据检查,使程序可能出现错误进行排查,所以const与define之间的区别在于const定义常量排除了程序之间的不安全性.
  5. const常量存在于程序的数据段,#define常量存在于程序的代码段
  6. const常量存在“常量折叠”,在编译器进行语法分析的时候,将常量表达式计算求值,并用求得的值来替换表达式,放入常量表,可以算作一种编译优化。因为编译器在优化的过程中,会把碰见的const全部以内容替换掉,类似宏。

1.2 sizeof

  1. sizeof关键字不会计算表达式的值,而只会根据类型推断大小。
  2. sizeof() 的括号可以省略, 如 sizeof a ; 
  3. 类A的大小是 所有非静态成员变量大小之和+虚函数指针大小

1.3 static 

static的用法有:

(1)声明静态全局变量,如static int a; 静态全局变量的特点:

  • 该变量在全局数据区分配内存; 
  • 未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化); 
  • 静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的; 

(2)声明静态局部变量,即在函数内部声明的,静态局部变量的特点:

  • 该变量在全局数据区分配内存; 
  • 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化; 
  • 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0; 
  • 它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;

(3)声明静态函数,限定函数的局部访问性,仅在文件内部可见

(4)类的静态数据成员,与全局变量相比,静态数据成员的好处有:

  • 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性; 
  • 可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能;

(5)类的静态方法

1.4 typedef 

typedef 用来定义新的类型,类似的还有#define 和 using (C++11) (应该尽可能用using ,比如 using AAA = int64_t; )

与宏定义的对比

  1. #define 在预处理阶段进行简单替换,不做类型检查; typedef在编译阶段处理,在作用域内给类型一个别名。
  2. typedef 是一个语句,结尾有分号;#define是一个宏指令,结尾没有分号
  3. typedef int* pInt; 和 #define pInt int* 不等价,前者定义 pInt a, b;会定义两个指针,后者是一个指针,一个int。

1.5 inline

inline用来向编译器请求声明为内联函数,编译器有权拒绝。

与宏函数的对比

  1. 内联函数在运行时可调试,而宏定义不可以;
  2. 编译器会对内联函数的参数类型做安全检查或自动类型转换(同普通函数),而宏定义则不会;
  3. 内联函数可以访问类的成员变量,宏定义则不能;
  4. 在类中声明同时定义的成员函数,自动转化为内联函数
  5. 宏只是预定义的函数,在编译阶段不进行类型安全性检查,在编译的时候将对应函数用宏命令替换。对程序性能无影响

不能声明为inline的函数

  1. 包含了递归、循环等结构的函数一般不会被内联。
  2. 虚拟函数一般不会内联,但是如果编译器能在编译时确定具体的调用函数,那么仍然会就地展开该函数。
  3. 如果通过函数指针调用内联函数,那么该函数将不会内联而是通过call进行调用。
  4. 构造和析构函数一般会生成大量代码,因此一般也不适合内联。
  5. 如果内联函数调用了其他函数也不会被内联。

1.6 static const \ const \ static 

1. static const 
static const 数据成员可以在类内初始化 也可以在类外,不能在构造函数中初始化,也不能在构造函数的初始化列表中初始化
2. static
static数据成员只能在类外,即类的实现文件中初始化,也不能在构造函数中初始化,不能在构造函数的初始化列表中初始化;
3. const
const数据成员只能在构造函数的初始化列表中初始化;

1.7 explicit 

explicit禁止了隐式转换类型,用来修饰构造函数。原则上应该在所有的构造函数前加explicit关键字,当你有心利用隐式转换的时候再去解除explicit,这样可以大大减少错误的发生。如果一个构造函数 Foo(int) ;则下面的语句是合法的:

Foo f; 

f = 12; // 发生了隐式转换,先调用Foo(int)用12构建了一个临时对象,然后调用赋值运算符复制到 f 中

如果给构造函数加了explicit,即 explicit Foo(int);就只能进行显示转换,无法进行隐式转换了:

f = 12; // 非法,隐式转换

f = Foo(12); // 合法,显示转换

f = (Foo)12;//合法,显示转换,C风格

1.8 extern 

extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定

typedef 数据类型 数据类型的别名

typedef的使用非常简单,在typedef关键字之后,分别跟上数据类型和相应的绰号就定义了这个数据类型的别名,在接下来的程序中就可以使用这个别名来指代这个数据类型了。

在武林中,是给大名鼎鼎的人物取外号,为的是唬人;而在C++世界中,是为那些比较复杂难以书写的数据类型取外号,为的是偷懒。例如,觉得unsigned char无符号字符类型的书写比较烦琐时,可以使用typedef为它定义一个简单易记的别名,然后使用这个别名作为数据类型来定义无符号字符类型的变量:

// 为无符号字符类型unsigned char定义一个别名uchar

typedef unsigned char uchar;

有了这个简单的别名,就可以用它来指代无符号字符类型,用作数据类型定义变量:

// 定义一个uchar类型的变量,实际上就是unsigned char类型的变量

uchar a;

使用别名之后的程序代码,是不是书写起来更加简单,同时也更加简洁易懂呢?所以,在以后遇到类似情况的时候,使用typedef为复杂类型取一个简单的别名,不仅自己写起来很方便,别人读起来也很轻松。这样两全其美的事情,何乐而不为呢?

这里大家可能有个疑问,利用前面学习的宏,将复杂类型定义成一个宏,不也同样可以达到化繁为简的目的吗?它们两个有什么区别呢?typedef是为复杂数据类型定义一个别名,而不只是像宏一样简单的替换。这一点在同时定义指针类型的多个变量时特别有用。例如,想定义两个int*指针类型的变量,自然地,使用宏我们可能会这样写:

// 定义指针类型的宏
#define PINT int*
// 使用宏定义两个变量
PINT pInt1,pInt2;

然而,这段看起来再正确不过的代码,实际的效果却与我们的预想大相径庭。经过宏替换后,上面定义指针变量的代码变为:

// 宏替换后的实际代码

int* pInt1, pInt2;

这不是在定义两个指针变量,而是在定义一个int指针类型变量pInt1和另一个int类型变量pInt2。想使用宏在同一行内方便地定义多个指针变量是行不通的,解决问题的办法就是用typedef为指针类型定义一个别名,然后使用这个别名作为数据类型,就可以在一行内定义多个指针类型的变量了:

// 为指针类型int*定义一个别名PINT

typedef int* PINT;

// 同时定义多个指针类型变量

PINT pInt1, pInt2;

这时,PINT已经成为一种新的数据类型,自然它可以在同一行内同时定义多个PINT类型的变量,而这种新类型本质上还是int指针类型,也就相当于同时定义了多个int指针类型的变量。

typedef的另外一个重要用途是为复杂的类型定义简单的别名。请看下面这行代码:

int* (*pFunc)(int, char*);

实际上,这行代码所定义的是一个函数指针pFunc,它所能够指向的函数的返回值类型是int*,两个参数分别是int类型和char*类型。如果只是定义一个这种类型的函数指针,那还可以勉强接受;如果要定义多个,那么多次书写这么复杂的既难写又难懂的语句,恐怕只有“撞墙”了。好在使用typedef可以为这种复杂的类型定义一个简单的别名,使用这个别名就可以轻松定义多个这种类型的变量。

// 定义函数指针类型为PFUNC

typedef int* (*PFUNC)(int, char*);

 
// 使用PFUNC定义多个函数指针变量

PFUNC pFunc1, pFunc2;
posted @ 2020-10-20 09:51  konglingbin  阅读(1134)  评论(0编辑  收藏  举报