C++_chapter2_C++基础知识点

本章总结了C++的基本知识点,下面分别从函数,const和volatile,C++调用C, const用法,左右和右值引用,new和delete等几个方面来写。

文章目录

  • [2.1 函数](#2.1 函数)
  • [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

本节参考:
https://blog.csdn.net/weixin_59409001/article/details/146112651?ops_request_misc=&request_id=&biz_id=102&utm_term=C++%20%E4%B8%AD%20volatile%20&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-0-146112651.142v102pc_search_result_base3&spm=1018.2226.3001.4187

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 与 普通变量区别?

  1. C++中的const 必须被初始化,如果初始化为一个立即数,那么被const修饰的变量就是一个常量,可以作为数组的下标,是真正的const,const应该被存放在了.text段,不能作为左值。
  2. C++中如果被const修饰的变量初始化为变量,比如,const int a = b; 那么a就是一个常变量,与C语言中的const相同;
  3. 编译方式不同,C++中用到const修饰的变量,会被替换为常量。
  4. 代码如下:
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 * const
q = &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++左值引用和指针的区别

  1. 引用是一种更安全的指针。因为引用必须被初始化,指向一块内存空间,是这块内存空间的别名;而指针初始可以为空,所以引用更安全一些;
  2. 引用只有一级引用;指针可以有多级指针,而引用只有一级引用。
  3. 定义一个引用变量和定义一个指针变量,其汇编指令一模一样,如下图所示。
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在内存中的临时变量的地址。

  1. 右值引用变量本身也是一个左值(因为为10开辟了内存空间,然后右值引用指向了该内存空间,所以右值引用相当于这块没有名字的内存空间的别名,所以右值引用可以作为左值使用)。

  2. 不能用右值引用变量,引用左值。【因为右值引用相当于生成了一个临时变量,而左值已经是个变量,不必生成临时变量,所以右值引用不能引用左值】

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;
相关推荐
ha20428941945 小时前
Linux操作系统学习之---基于环形队列的生产者消费者模型(毛坯版)
linux·c++·学习
渡我白衣8 小时前
C++ 同名全局变量:当符号在链接器中“相遇”
开发语言·c++·人工智能·深度学习·microsoft·语言模型·人机交互
是那盏灯塔9 小时前
【算法】——动态规划之01背包问题
数据结构·c++·算法·动态规划
迷失的walker10 小时前
【Qt C++ QSerialPort】QSerialPort fQSerialPortInfo::availablePorts() 执行报错问题解决方案
数据库·c++·qt
南方的狮子先生11 小时前
【数据结构】(C++数据结构)查找算法与排序算法详解
数据结构·c++·学习·算法·排序算法·1024程序员节
紫荆鱼11 小时前
设计模式-适配器模式(Adapter)
c++·设计模式·适配器模式
报错小能手13 小时前
C++笔记(面向对象)详解单例模式
c++·笔记·单例模式
吗~喽13 小时前
【C++】内存管理
c++
上去我就QWER14 小时前
解锁Qt元对象系统:C++编程的超强扩展
c++·qt