C++
开始
1. C++
关键字
C++98
里共63个,其中32个是C
语言的。现在最新版本有68个。
2. 命名空间(namespace
)
避免命名冲突和名字污染。在大型工程中变量/函数非常多,每一个模块都有大量的文件,大量的变量/函数/结构体/…,这时非常容易引起命名冲突。
引入命名空间就是解决命名冲突,不同命名空间中的变量可以一样。(docker
中的隔离性!!) 一个命名空间就是一个新的作用域。
域作用限定符:::
, A::
, B::
, ::
(默认的是全局域)
#include <iostream> namespace A { int a = 10; int b = 20; } int main() { int a = 1; int b = 2; // 这个作用域里的a和b std::cout << a + b << std::endl; // 命名空间A里的a和b std::cout << A::a + A::b << std::endl; return 0; }
// 把 std(标准库) 里的变量全部放出来 using namespace std; // 只放出来常用的 using std::cout; using std::endl;
几点注意:
- 命名空间可以嵌套定义。
- 一个工程中有相同的命名空间时会合并。
- 命名空间只能在全局声明,不能在
main
函数里。 - 命名空间中的变量本质上还是全局的变量,只是使用命名空间进行隔离。
3. 输入输出
这两个运算符涉及到==运算符的重载。==正是因为重载,他们才可以自动识别类型。
// << 流插入 std::cout << "hello world" << std::endl; // >> 流提取 int a, b; cin >> a >> b;
4. 缺省参数
在函数形参那里可以指定一个缺省值,当实参那里没有传参数时就使用这个缺省参数,实参传了参数就用传过来的那个参数。
// 全缺省 void func(int a = 10, char ch = 's') { // ... } // 半缺省 void func2(int a, int b, char ch = 's') { // ... } int main() { // 都可以 func(); func(1); func(1, 'a'); func2(1, 2); func(1, 2, 'a'); }
- 可以全缺省,也可以半缺省(缺省一部分形参),但是半缺省时必须从最后一个参数开始顺序向前开始缺省!!就是从右向左依次给缺省值。(因为参数是从左往右依次传的)
- 当**一个函数声明和定义分开时,不能同时给缺省值!!**编译器不知道听谁的。这个时候应该是声明的时候写好缺省值,定义的时候不写缺省。(经过检验,在VS和Linux下同时给缺省是可以的,以声明的值为准!)
- 给定的缺省值只能是常量或全局变量。
5. 函数重载(重点)
在同一个作用域中可以定义多个同名的函数,但是函数的参数列表(参数个数/参数类型)必须不同,叫做函数重载。
C++支持重载的原因(面试题)
1. 程序的编译链接过程(预编译->编译->汇编->连接)
- 预处理(预编译):进行头文件展开,宏替换,条件编译,去掉注释…
- 编译:进行语法检查,语义分析,词法检查… 最终生成汇编代码。
- 汇编:形成符号表(函数名和对应的地址之间的映射),最终生成二进制文件。
- 连接:合并符号表,并进行符号表的重定位,就是将各个文件里只有函数声明,没有具体定义的那些函数,去符号表查找,找到其具体的地址!! 最终生成可执行文件(.o)。
2. C++支持重载具体原因
C++编译器(g++
)在编译时会对函数名进行修改:
// 如下 Printf(int a, double b) -> _Z5Printid
具体修改规则(Linux下):_Z + 函数名长度(协议) + 函数名 + 所有参数类型缩写
这样就可以将重载的函数区分开。而C编译器(gcc
)在编译时不会对函数名做修改,相同的函数名就无法区分,也就不能支持重载。
Linux下反汇编:
objdump -S[-c -t ....] filename > filename.txt
3. extern “C”
在实际中,可以使用C语言调用C++的动静态库(多个 .o 打包),也可以使用C++调用C语言的动静态库。但是:==C动静态库中编译后的函数名是没有经过更改的原始的函数名,而C++的动静态库中编译后的函数名是经过编译器修改后的函数名!==这和C/C++项目本身的编译规则是不一样的,这时就会导致链接错误----在链接时找不到符号表中对应的函数名!
这时就可以使用 extern "C"
来解决。
C++调用C
修改这个C++文件/项目。
// 在C++文件中 加这个extern "C"后{}内的内容就会以C语言的规则编译(不修改函数名) // 这样就可以避免C++编译时修改函数名而导致链接时在符号表中找不到对应的函数 extern "C" { // 这个库里文件就会以C的方式编译,不修改函数名 #include "/home/c_lib/.... .h" }
C调用C++
对C++的动静态库进行修改!!都是对C++的那一方进行修改。
利用条件编译!条件编译在跨平台方面非常有用!!
// 在C++库文件中(头文件) #ifdef __cplusplus // 在C++文件中默认有这个定义 #define EXTERN_C extern "C" #else #define EXTERN_C #endif // 这些函数声明在C++库文件中就会以C的方式编译,不会对函数名做修改! // 而在C的项目中包含这个头文件,展开后 EXTERN_C 是空!!也不会有任何问题! EXTERN_C 函数声明 EXTERN_C 函数声明 EXTERN_C 函数声明 ......
==本质就是为了做到在C++中以C的方式编译,同时不影响C包含C++的头文件而出错!!==或者这样也可以:
// 本质就是为了做到在C++中以C的方式编译,同时不影响C包含C++的头文件而出错!! #ifdef __cplusplus extern "C" { #endif 函数声明 函数声明 函数声明 // ..... #ifdef __cplusplus } #endif
这时C++库中就不能使用函数重载了!!因为以C的方式编译不修改函数名,不支持重载!
6. 引用
1. 基本概念
就是给一个变量起别名。常用作函数形参和函数返回值,减少参数拷贝带来的消耗(函数调用会将实参拷贝到形参)。
做函数返回值时要特别注意一下,如果返回的变量在出了函数作用域后就销毁了,那就会有类似野指针的问题!(引用的对象销毁了!)
函数返回值问题:函数的返回值并不是直接返回的,而是先将返回值拷贝到一个临时变量(一般临时变量有常属性)。当返回值比较小时就可以拷贝到寄存器中;当返回值很大时会在 调用函数 建立栈帧的时候就提前开辟好这个空间!然后出了函数作用域这个返会的变量可能被销毁,所以再将这个临时变量拷贝到接收返回值的变量中!!
这就导致中间多了一步拷贝,当返回值比较大时效率很低!传引用返回就可以提高效率,直接就返回这个变量的别名(就是这个变量)。但是要求函数返回之后这个变量还在,没有被销毁!
**常引用:**这个还是很常用的,可以接收变量,常量,隐式类型转换等类型。
void TestConstRef() { const int a = 10; // int& ra = a; // 该语句编译时会出错,a为常量 const int& ra = a; // 可以 // int& b = 10; // 该语句编译时会出错,10为常量 const int& b = 10; // 可以 double d = 12.34; // int& rd = d; // 该语句编译时会出错,类型不同 // 这个可以!!但是 rd 已经不是d这个变量的引用了!而是会产生一个int类型的临时 变量,rd是那个临时变量的引用。 const int& rd = d; // 这个留意一下!! }
引用的几点注意:
- 引用在定义的时候必须初始化。
- 一旦引用了某一个实体,就不能改变,一直作为他的引用!
- 引用在语法层面就是一个别名,没有独立的空间,但是底层还是指针!!
2. 指针和引用的区别:
- 没有
NULL
引用,但是有NULL
指针。 sizeof
(引用)大小是所引用类型的大小,sizeof
(指针)在32位平台下是4字节。- 引用进行加减操作是对本体进行加减,指针进行加减操作是向后/前 偏移一个类型大小。
7. 内联函数(inline
)
使用关键字inline
修饰的函数叫做内联函数,在编译时会直接在调用内联函数的地方把函数体展开,就不需要建立栈帧,进行传统的函数调用了!!提高运行效率(空间换取时间)
- 适用于频繁调用的短小函数(非递归的)。
inline
也只是一个建议,具体编译器会不会采用是不确定的!inline
函数不能声明和定义写到不同的文件中!会有链接错误:定义内联函数的文件在生成.o
目标文件时在符号表里不会生成该函数的地址(因为是内联函数),这样在链接的时候就找不到该函数了。
inline Add(int a, int b) { return a + b; }
在C语言中使用宏来做到这一点。宏的缺点:
- 不支持调试。
- 没有类型安全检查。
8. auto
关键字
自动进行类型推导。
1. auto不能推导的场景
- 函数形参的类型不能用
auto
(C++20 以后也可以了) - 函数返回值不能是
auto
- auto不能直接用来声明数组。
// 获取变量类型 int a = 10; cout << typeid(a).name() << endl;
9. 范围 for
// 自动遍历 for(auto& e : arr) cout << e << endl;
10. nullptr
空指针。更推荐!
#ifndef NULL #ifdef __cplusplus #define NULL 0 // 在C++中,NULL就是0 #else #define NULL ((void *)0) #endif #endif
NULL
是一个宏,C++中NULL
就是 0,是一个整型,这就可能会有问题。比如:
void f(int) { cout<<"f(int)"<<endl; } void f(int*) { cout<<"f(int*)"<<endl; } int main() { f(0); // f(int) f(NULL); // f(int) 但是直观来看,这里应该是 f(int*) f((int*)NULL); // f(int*) return 0; }
而 nullptr
是一个关键字!使用更安全。
NULL
是一个宏,C++中NULL
就是 0,是一个整型,这就可能会有问题。比如:
void f(int) { cout<<"f(int)"<<endl; } void f(int*) { cout<<"f(int*)"<<endl; } int main() { f(0); // f(int) f(NULL); // f(int) 但是直观来看,这里应该是 f(int*) f((int*)NULL); // f(int*) return 0; }
而 nullptr
是一个关键字!使用更安全。
最后挂个链接,欢迎一起学习,一起进步!https://xxetb.xet.tech/s/4G6TWG