本章总结了C++的基本知识点,下面分别从函数,const和volatile,C++调用C, const用法,左右和右值引用,new和delete等几个方面来写。
文章目录
- [2.1 函数](#2.1 函数)
-
- [2.1.1 函数调用过程](#2.1.1 函数调用过程)
- [2.1.2 inline内联函数](#2.1.2 inline内联函数)
- 2.1.3详解函数重载
- [2.2 volatile](#2.2 volatile)
-
- [2.2.1 volatile作用](#2.2.1 volatile作用)
- [2.2.2 语法](#2.2.2 语法)
- [2.2.3 volatile使用](#2.2.3 volatile使用)
- [2.2.4 注意事项](#2.2.4 注意事项)
- [2.3 C++调用C语言](#2.3 C++调用C语言)
- [2.4 const用法](#2.4 const用法)
-
- [2.4.1 C++ const 与 普通变量区别?](#2.4.1 C++ const 与 普通变量区别?)
- [2.4.2 const和一级指针](#2.4.2 const和一级指针)
-
- [2.4.2.1 const修饰的量常出现的错误](#2.4.2.1 const修饰的量常出现的错误)
- [2.4.2.2 const和一级指针的结合:有4种情况](#2.4.2.2 const和一级指针的结合:有4种情况)
- [2.4.3 const技巧](#2.4.3 const技巧)
- [2.4.4 const 与一级指针转换公式](#2.4.4 const 与一级指针转换公式)
- [2.4.5 const 与二级指针结合的3种转换公式](#2.4.5 const 与二级指针结合的3种转换公式)
- [2.4.6 const二级指针的分析](#2.4.6 const二级指针的分析)
- [2.4.7 类型转换面试题](#2.4.7 类型转换面试题)
- 2.5左值引用和右值引用
-
- [2.5.1 C++左值引用和指针的区别](#2.5.1 C++左值引用和指针的区别)
- [2.5.2 右值引用(可以引用立即数)](#2.5.2 右值引用(可以引用立即数))
- [2.5.3 const、指针、引用的结合使用](#2.5.3 const、指针、引用的结合使用)
- [2.5.4 指针和引用的转换](#2.5.4 指针和引用的转换)
- [2.6 new和delete](#2.6 new和delete)
-
- [2.6.1 面试题:new delete 和 free malloc区别?](#2.6.1 面试题:new delete 和 free malloc区别?)
- [2.6.2 面试题:C++有几种new](#2.6.2 面试题:C++有几种new)
2.1 函数
从三个方面介绍函数,分别是函数参数,inline内联函数和函数重载。
2.1.1 函数调用过程
函数的调用过程
下面给出了Add的带一个参数,带两个参数和不带参数的调用过程.
cpp
int Add(int a = 10, int b = 20)
{
return a + b;
}
int main()
{
int a = 10;
int b = 20;
Add(a, b);
/* 实参入栈
mov eax, dword ptr[ebp-8]
push eax
mov ecx, dword ptr[ebp-4]
push ecx
call Add
*/
Add(a);
/*
mov ecx, dword ptr[ebp - 4]
push ecx
call Add
*/
Add();
/*
push 14H
push 0Ah
call Add
*/
return 0;
}
从汇编层面可以看到调用参数时候,不带参数比带参数效率高。函数默认的参数的效率比带参数时候,效率高。
注意:给默认值时候,从右向左给。
函数调用效率的问题
调用带默认值的函数效率比调用不带默认值的效率高,因为带默认值参数的函数指令更少。
函数声明也可以直接给默认值
声明时候,也可以带默认值,但是函数实现的时候,不需要带默认值了。
cpp
int sum(int a = 10,int b = 20)
2.1.2 inline内联函数
inline内联函数和普通函数区别?
inline函数不生成相应的函数符号,也就没有了普通函数的调用开销。
比如调用int ret = Add(a, b);调用Add函数时,这是一个标准调用,需要参数压栈,开辟Add函数栈帧,对栈帧初始化,执行函数中的指令,然后再回退到调用点,释放栈帧;而inline内联函数省去了函数调用开销,在函数的调用点,直接把函数的代码进行展开。
cpp
mov ebp,esp
sub esp,4ch
rep stos 0xCCCCCC 初始化
....
mov esp,ebp
mov

注意:inline函数在release下才起作用,debug调试下不起作用。
2.1.3详解函数重载
函数重载条件
C++代码产生函数符号的时候,函数名+参数列表类型组成的!
C代码产生函数符号的时候,函数名来决定!
函数重载代码实验
在同一个作用域内,一组函数,其中函数名相同(返回值不作为重载依据),参数列表的个数不同,这样的一组函数称为函数重载。

不能重载的问题
一组函数,函数名称相同,作用域相同,仅仅是返回值不同,不可以作为重载的条件。因为C++在生成符号时,按照函数名+参数的方式生成函数的符号;而C语言则是按照函数名的方式生成符号。
从生成函数符号的条件可知,函数符号不依赖于返回值,所以返回值不能作为函数重载的条件。
2.2 volatile
2.2.1 volatile作用
前景知识:通常情况下,编译器为了提高性能,会对代码进行各种优化,如内存序优化,将变量的值缓存到寄存器中;
避免每次从内存中读取等。
volatile 告诉编译器,不要对volatile修饰的变量优化。
2.2.2 语法
volatile 数据类型 变量名;
volatile 数据类型 * 变量名;
例如:
volatile int id;
volatile int *p = &sensorValue;
2.2.3 volatile使用
下面例子,sensorRegistorValue 声明为 volatile,这个值可能随时被改变,如果不使用volatile关键字,编译器编译时可能将 sensorRegistorValue 进行优化,例如将值缓存到寄存器中,每次读取时每次直接从寄存器中读取,而不是每次都从内存中读取。
cpp
volatile int sensorRegistorValue = 1;
void readRegistorValue()
{
cout << "sensor value = " << sensorRegistorValue << endl;
}
void test()
{
sensorRegistorValue = 2;
readRegistorValue(); // 改变值
}
在多线程中使用
cpp
// 在多线程中使用
volatile bool flag = false;
void workerThread() {
std::this_thread::sleep_for(std::chrono::seconds(2));
flag = true; // 修改标志位
std::cout << "Flag is set to true." << std::endl;
}
void test()
{
std::thread t(workerThread);
while (!flag)
{
// 等待标志位被设置
}
std::cout << "Main thread detected flag change." << std::endl;
t.join();
/*
在这个示例中,flag 被声明为 volatile,
确保主线程在每次循环中都会从内存中读取 flag 的最新值,而不是使用寄存器中的缓存值,
从而保证主线程能够及时响应 workerThread 对 flag 的修改。
*/
}
2.2.4 注意事项
1 虽然,volatile 可以确保变量的每次读写都直接访问内存,但它并不能保证线程安全。
在多线程环境中,如果多线线程访问同一个 volatile 变量,对同一个变量进行读写,并不能保证线程安全。
2 volatile 会禁止编译器对修饰的变量进行优化,可能会影响程序性能。
因此,只有在确实需要防止编译器优化,且变量的值可能会被外部因素改变的情况下才使用 volatile。
3 volatile 可以和 const 一起使用,例如 const volatile int,表示该变量的值不能被程序修改,但可能会被外部因素改变。
2.3 C++调用C语言
想在cpp中,调用C函数,就要在C++中编译时候,告诉C++编译器按照C语言方式进行编译,所以在.cpp中的接口中加上下面这句话。


2.4 const用法
2.4.1 C++ const 与 普通变量区别?
- C++中的const 必须被初始化,如果初始化为一个立即数,那么被const修饰的变量就是一个常量,可以作为数组的下标,是真正的const,const应该被存放在了.text段,不能作为左值。
- C++中如果被const修饰的变量初始化为变量,比如,const int a = b; 那么a就是一个常变量,与C语言中的const相同;
- 编译方式不同,C++中用到const修饰的变量,会被替换为常量。
- 代码如下:
cpp
int main()
{
const int a = 20;
int *p = (int *)&a;
*p = 30;
cout << a << "," << *p << "," << *(&a) << endl;
system("pause");
return 0;
}
2.4.2 const和一级指针
2.4.2.1 const修饰的量常出现的错误
1.常量不能再作为左值
2.不能把常量的地址泄露给一个普通的指针 或者 普通的引用变量。
cpp
const int a = 10;
int *p = &a; // 这种情况不被允许;
2.4.2.2 const和一级指针的结合:有4种情况
我们关注的是,const 与最近修饰的表达式,不关注int,double等类型。
(1) const int p = &a;
可以任意指向不同的int类型的内存,但是不能通过指针间接修改指向的内存的值;
(2) int const p;
const 修饰的最近的表达式是 *p ,所以 (1)和 (2) 同;
(3) int *const p = &a; => p = &b 修改内存中的值:*p = 20;
const修饰的是p本身,这个指针p现在是常量,不能再指向其它变量,但是可以通过指针解引用修改指向的内存的值;
(4) const int const p = &a;
const如果右边没有指针 的话,const是不参与类型的;
这句话理解:第二个const指向了p,说明p不能再指向其他变量了;
第一个const类型为 const int * 表示指向的内容不能被修改;整体这句话相当于双重修饰,更严格的限定。

错误原因:const int ** 《====== int **
【深入理解一下int **, 内存模型如下:说明int a不能被修改,而实际情况是int a不是一个常整型】

2.4.3 const技巧
记住:如果const右边没有指针,const 是不参与类型的
cpp
int a = 10;
int *p1 = &a;
const int *p2 = &a; // const int * <=== int 可以
int *const p3 = &a; // *p = &a; 可以
int *const p3 = &a; // int * const -> 等价于 int * -===>int *
int *p4 = p3 ; // 同理,也是 int * 转为 int *
2.4.4 const 与一级指针转换公式
1 int* <= const int* 是错误的, 因为将常量地址暴露给普通指针,不能被允许;
2 const int * <= int* 是可以的!
2.4.5 const 与二级指针结合的3种转换公式
cpp
int a = 10;
int *p = &a;
const int **q = &q;
const 与 二级指针的 3种表达式
const int **q; // const **q 不能被赋值,*q 和 q 都可以被赋值
int * const *q; // const 修饰 *q ,所以 *q不能被赋值,q 和 **q 可以被赋值
int **const q; // const q 不能被赋值,**q *q都可以赋值
(1)int** <= const int** 是错误的!
(2) const int** <= int是错误的!
// Andy: const 二级指针结合后的转换必须两边都有const
(3)int <= intconst 是错误的!
这是const和一级指针结合,相当于 int * 和 const * 这种方式;
【int * const *p, 主要看const后边的表达式,p】
(4)int * const * <= int**是可以的!
const后面只有 就相当于,const * 和 int *结合,是可以的;
【相当于从int * const int *】
(5)int * const * 《==== const int ** 不可以
【int * const *这个表达式相当于第一级指针是const int *类型的,第二级指针是int **;而const int **p的第一级指针是int *,第二级指针是const **; 】
【错误原因: const ** 不能转为 int ** 】
2.4.6 const二级指针的分析
cpp
int a = 10;
int *p = &a;
const int **q = &p; // 这句话错误,把常量的地址暴露给了变量。
*q <---> p,相当于同时指向了同一块内存,这块内存存放一级指针
const int * q = &p ;
如果上面第三句成立的话,相当于p可以修改 q的值,相当于把常量的地址暴露给了普通变量。
上面代码有两种修改方式:
cpp
int a = 10;
const int *p = &a;
const int **q = &p;
或者:
cpp
int a = 10;
int *p = &a;
const int * const*q = &p; // 不暴露自己的常量地址,
2.4.7 类型转换面试题
(1)
int a = 10;
const int p = &a; // const int <= int* 可以
int q = p; // int <= const int* ,这是错误的,*q = 20;
(2)
int a = 10;
int const p = &a; // const p 相当于 int <= int可以
int q = p; // int <= int 可以
(3)考const是否参与类型
int a = 10;
int *const p = &a; // int * --- int * 可以
int *const q = p; // int * 《---- int 可以
(4)
int a = 10;
int const p = &a; // int <= int*
const int q = p; // const int <= int* 可以
(5)const 内存暴露给了普通变量,有两种修改方式,见上面的笔记
int a = 10;
int *p = &a;
const int **q = &p;
const int * const *q2 = &p; // 修改方式1
(6)
int a = 10;
int p = &a;
int * constq = &p; // int const* <= int * 可以
(7)
int a = 10;
int p = &a;
int const q = &p; // int <= int* 可以
(8)
int a = 10;
int * const p = &a; // int* <= int* 可以
int **q = &p; // <= const int ** <===== int * const * 不行
&p 相当于 int * const * p,相当于 const * 转为 int *
(9)
int a = 10;
const int p = &a; // Andy: 这个合法
int * const q = &p; // int * const * <== const int * *
&p相当于,const int **p,
int * const *q 相当于 const *p 一级指针,二级指针是int **;
const int **p 一级指针是int *, 二级指针 const int **;
错误原因:相当于 const ** 转为 int **
2.5左值引用和右值引用
2.5.1 C++左值引用和指针的区别
- 引用是一种更安全的指针。因为引用必须被初始化,指向一块内存空间,是这块内存空间的别名;而指针初始可以为空,所以引用更安全一些;
- 引用只有一级引用;指针可以有多级指针,而引用只有一级引用。
- 定义一个引用变量和定义一个指针变量,其汇编指令一模一样,如下图所示。
cpp
Int a = 10;
Int *p = &a;
Int &b = a;

汇编指令过程:将a内存地址放入eax中;再将eax 放入b指向的内存中;

2.5.2 右值引用(可以引用立即数)
1 int &&a = 10; int const &d = 10; 这两句代码都可专门用来引用立即数,指令的实现原理是:编译器先为10开辟一个临时变量,存放10,a在内存中存放的是10在内存中的临时变量的地址。

-
右值引用变量本身也是一个左值(因为为10开辟了内存空间,然后右值引用指向了该内存空间,所以右值引用相当于这块没有名字的内存空间的别名,所以右值引用可以作为左值使用)。
-
不能用右值引用变量,引用左值。【因为右值引用相当于生成了一个临时变量,而左值已经是个变量,不必生成临时变量,所以右值引用不能引用左值】
2.5.3 const、指针、引用的结合使用
判断下面指针和引用的转换:
1
cpp
int a = 10;
int *p = &a;
const int *&q = p; 将引用转为指针:int **q = &p; // *q =
const int **q = &p; //const int** <= int** 不可以
2
cpp
int a = 10;
const int *p = &a;
int *&q = p; //int** <= const int** 不可以
3
cpp
int a = 10;
int *const p = &a;
int *&q = p; 转为
// int **q = &p; -> int ** 《====== int * const * 又等于 int * 《--- const *
4 在内存0x0018ffff 写入四字节的10
有三种方式:

2.5.4 指针和引用的转换
cpp
int a = 10;
int *p = &a;
int *&q = p; // 将这句话转为如下:
// int **q = &p <----》 int * &q = p;
2.6 new和delete
2.6.1 面试题:new delete 和 free malloc区别?
1 malloc 和 free 称为C的库函数;new和delete称为C++的运算符;
2 new不仅可以做内存开辟,还可以做内存初始化;下边代码,通过new为p1分配内存,并做初始化为20;
cpp
int *p1 = new int(20);
delete p1;
delete时,调用析构函数。
3 malloc开辟内存失败,通过返回值与NULL比较;而new 开辟内存失败,会抛出bad_alloc异常;通过该类型做判断.
2.6.2 面试题:C++有几种new
1 初始化new
cpp
int *p1 = new int(20); // 初始化20操作
2 不抛出异常,返回值为nulptr
cpp
int *p2 = new (nothrow) int;
3 常量new
cpp
const int *p3 = new const int(40);
4 定位new : 在指定的内存上开辟int空间,初始化50
cpp
int data = 0;
int *p4 = new (&data) int(50);
cout << "data:" << data << endl;