命名空间
在 C/C++ 中,变量、函数和类都是大量存在的,这些变量、函数和类的名称将都存在全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace 关键字的出现就是针对这种问题的。
定义
定义命名空间,需要使用到 namespace 关键字,后面跟命名空间的名字,然后接一对 {} 即可,{} 中即为命名空间的成员。
- 命名空间中可以定义变量/函数/类
- 命名空间可以嵌套
- 同一个工程中允许存在多个相同名称的命名空间,编译器会合成为一个命名空间
注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
使用方式
-
加命名空间名称及作用域限定符
-
使用 using 将命名空间中成员引入
-
使用 using namespace 命名空间名称引入
// 分别对应上面三种方式
// 1. std::cin
// 2. using std::cin
// 3. using namespce std
缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
分类
- 全缺省参数
- 半缺省参数
// 分别对应上面两种形式
// 1. void test1(int a = 1, int b = 2, int c = 3)
// 2. void test2(int a, int b = 2, int c = 3)
注意:
- 半缺省参数必须从右往左依次给出,不能间隔
- 一旦某个参数被赋予了默认值,它后面的所有参数都必须有默认值
- 缺省参数不能在函数声明和定义中同时出现
- 在给定的作用域中一个形参只能被赋予一次默认实参
- 局部变量不能作为默认实参
- 除此之外,只要表达式的类型能转化成形参所需的类型,该表达式就能作为默认实参
- 用作默认实参的名字在函数声明所在的作用域解析,而这些名字的求值过程发生在函数调用时
// 多次声明同一个函数,为其不同参数添加默认值是合法的,但是不推荐
void test(int a, int b = 2, int c = 3);
void test(int a = 1, int b, int c);
函数重载
自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。
概念
函数重载:是函数的一种特殊情况,C++ 允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或顺序)不同,返回值不作要求,常用来处理实现功能类似数据类型不同的问题。
重载示例:
int Add(int left, int right);
long Add(long left, long right);
double Add(double left, double right);
Name Mangling
C
早期 Unix 下的 C 语言因为历史原因规定,C 语言源代码文件中的所有全局变量和函数经过编译以后,相对应的符号前加上下划线 _
。在现在 Linux 下的 GCC 编译器中,默认情况已经去掉了在 C 语言符号前面加下划线的方式,而是直接使用函数名。
C 语言不支持函数重载,因为 C 语言直接使用函数名去表示和查找,而重载函数函数名相同。编译的时候,两个函数名相同的函数,在符号表中存在歧义和冲突,其在链接的时候也存在歧义和冲突。
C++
C++ 的目标文件符号表中不是直接用函数名来标识和查找函数。
g++ 的函数名修饰规则:
- 所有的符号都以
_Z
开头,对于嵌套的名字(在名称空间或在类里面的),后面紧跟 N,然后是各个名称空间和类的名字,每个名字前是名字字符串长度,再以 E 结尾 - 对于一个函数来说,它的参数列表紧跟在函数名 或 E 后面
- 全局变量和静态变量也有同样的机制,不过修饰并没有使用变量类型
有了函数名修饰规则,只要参数列表不同,符号表存储的修饰后的函数名就不同,也就不存在二义性和冲突了。
extern “C”
有时候在 C++ 工程中可能需要将某些函数按照 C 的风格来编译,在函数前加 extern “C”,意思是告诉编译器,将该函数按照 C 语言规则来编译。
那么 C 语言的项目可以使用 C++ 编写的库吗?
是可以的,库是一种二进制文件,只要能够找到相应的函数就可以使用。若有函数重载的话,是没办法使用的。
引用
概念
引用不是新定义一个变量,而是给已存在变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
语法:类型& 引用变量名 = 引用实体
int a = 10;
int& ra = a;
引用类型必须和引用实体是同种类型的。
特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
使用场景
- 做参数(提高效率,形参的改变可以影响实参)
- 做返回值(提高效率,修改返回变量)
注意:如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
传值和传引用效率比较
以值作为参数或返回值类型,在函数传参/返回时,不会直接传递实参/将变量本身直接返回,而是传递/返回一份变量的临时的拷贝,因此用值作为参数/返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时。
指针和引用的区别
在语法概念上引用就是一个别名,没有独立空间,和其引用体公用一块空间。
在底层实现上实际是有空间的,引用是按照指针的方式来实现的。
引用和指针的不同点:
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时绑定一个实体后,就不能再绑定其他实体,而指针可以改变指向
- 没有 NULL 引用,但有 NULL 指针
- 在 sizeof 中含义不同:引用结果为引用类型的大小,但指针始终是指针类型所占字节个数
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
内敛函数
概念
以 inline 修饰的函数叫做内联函数,编译时 C++ 编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数可以提升程序运行的效率。
特性
- inline 是一种以空间换时间的做法,省去调用函数额开销,增加了内存和磁盘占用,所以代码很长或者有循环/递归的函数不适宜使用作为内联函数
- inline 对于编译器而言只是一个请求,编译器会自动优化,如果定义为 inline 的函数体内有循环/递归等等,编译器优化时会忽略掉内联请求
- inline 不能将声明和定义分离,分离会导致链接错误
功能
宏的优点:
- 增强代码的复用性
- 提高性能
宏的缺点:
- 不方便调试宏(预处理阶段进行了替换)
- 导致代码可读性差,可维护性差,容易误用
- 没有类型安全的检查
函数定义换用内联函数
- 在类内定义的成员函数会默认加上内敛(隐式内敛)
- 内敛声明定义必须一起,否则不起作用
常量定义换用 const
C++ 中被 const 修饰的变量:编译器看到变量被 const 修饰,默认其不会改变,若后面有用到该变量会将其值放入寄存器中,使用时直接读取寄存器中数据,而不是到内存中读取。
-
编译器一般不会为整数型 const 对象在内存中开辟空间,除非指针或引用等必须用它的地址的情况
-
可以用 volatile 修饰该变量,让每次取值都必须到内存中读取
#include <iostream>
using namespace std;
int main() {
// volatile const int a = 10;
const int a = 10;
int* p = (int*)(&a);
*p = 20;
// 此时打印 a 的值是 10, *p 的值是 20
cout << a << " " << *p << endl;
return 0;
}
constexpr
常量表达式(const expression)是指值不会改变并且在编译过程就能得到计算结果的表达式。
一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定,例如:
const int num1 = 1230; // num1 是常量表达式
const int num2 = num1 + 1; // num2 是常量表达式
int num3 = 1002; // num3 不是常量表达式
const int num4 = GetNum(); // num4 不是常量表达式
尽管 num3 的初始值是个字面值常量,但由于它的数据类型只是普通的 int 而非 const int,所以它不属于常量表达式。另一方面,尽管 num4 本身是一个常量,但它的具体值直到运行时才能获取到,所以也不是常量表达式。
constexpr 变量
C++ 11 新标准规定,允许将变量声明为 constexpr 类型以便编译器来验证变量的值是否是一个常量表达式。声明为 constexpr 的变量一定是一个常量,而且必须用常量表达式初始化。
constexpr int num1 = 1230; // 1230 是常量表达式
constexpr int num2 = num1 + 1; // num1 + 1 是常量表达式
constexpr int num3 = GetNum(); // 只有当 GetNum 是一个 constexpr 函数时,才是一条正确的声明语句
指针和引用都能定义成 constexpr,但它们的初始值却受到严格的限制。一个 constexpr 指针的初始值必须是 nullptr 或 0,或者是存储于某个固定地址中的对象。
- 固定地址:函数体外定义的对象(全局对象)、static 修饰的对象
- constexpr 修饰指针,只限定指针的指向不能更改,对指向空间的值不作限制
constexpr 函数
constexpr 函数(constexpr function)是指能用于常量表达式的函数。
需要遵守两个约定:
- 函数的返回类型及所有形参的类型都得是字面值类型
- 函数体中必须有且只有一条 return 语句
执行初始化任务时,编译器把 constexpr 函数的调用替换成其结果值。为了能在编译过程中随时展开,constexpr 函数被隐式地指定为内敛函数。constexpr 函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行。例如,空语句、类型别名以及 using 声明。
nullptr
由于 C++ 中 NULL 被定义成字面量 0,这样就可能回带来一些问题,因为 0 既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11 中新增了 nullptr,用于表示空指针。
//stddef.h
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
注意:
- 在使用 nullptr 时,不需要包含头文件,因为 nullptr 是 C++11 新增的关键字
- 在 C++11 中,
sizeof(nullptr)
与sizeof((void*)0)
所占的字节数相同 - 为了提高代码的健壮性,在表示指针空值时建议使用 nullptr