70道C语言与C 常见问答题(上)

1 变量的声明和定义有什么区别

变量的定义为变量分配地址和存储空间, 变量的声明不分配地址。一个变量可以在多个地方声明, 但是只在一个地方定义。加入extern 修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。

说明:很多时候一个变量,只是声明不分配内存空间,直到具体使用时才初始化,分配内存空间, 如外部变量。

int main() {   extern int A;   //这是个声明而不是定义,声明A是一个已经定义了的外部变量   //注意:声明外部变量时可以把变量类型去掉如:extern A;   dosth(); //执行函数}int A; //是定义,定义了A为整型的外部变量

2 简述#ifdef、#else、#endif和#ifndef的作用

利用#ifdef、#endif将某程序功能模块包括进去,以向特定用户提供该功能。在不需要时用户可轻易将其屏蔽。

#ifdef MATH#include 'math.c'#endif

在子程序前加上标记,以便于追踪和调试。

#ifdef DEBUGprintf ('Indebugging......!');#endif

应对硬件的限制。由于一些具体应用环境的硬件不一样,限于条件,本地缺乏这种设备,只能绕过硬件,直接写出预期结果。

「注意」:虽然不用条件编译命令而直接用if语句也能达到要求,但那样做目标程序长(因为所有语句都编译),运行时间长(因为在程序运行时间对if语句进行测试)。而采用条件编译,可以减少被编译的语句,从而减少目标程序的长度,减少运行时间。

3 写出int 、bool、 float 、指针变量与 “零值”比较的if 语句

//int与零值比较 if ( n == 0 )if ( n != 0 ) //bool与零值比较 if   (flag) //   表示flag为真 if   (!flag) //   表示flag为假  //float与零值比较 const float EPSINON = 0.00001;if ((x >= - EPSINON) && (x <= EPSINON) //其中EPSINON是允许的误差(即精度)。//指针变量与零值比较 if (p == NULL)if (p != NULL)

4 结构体可以直接赋值吗

声明时可以直接初始化,同一结构体的不同对象之间也可以直接赋值,但是当结构体中含有指针“成员”时一定要小心。

「注意」:当有多个指针指向同一段内存时,某个指针释放这段内存可能会导致其他指针的非法操作。因此在释放前一定要确保其他指针不再使用这段内存空间。

5 sizeof 和strlen 的区别

  • sizeof是一个操作符,strlen是库函数。
  • sizeof的参数可以是数据的类型,也可以是变量,而strlen只能以结尾为‘\0’的字符串作参数。
  • 编译器在编译时就计算出了sizeof的结果,而strlen函数必须在运行时才能计算出来。并且sizeof计算的是数据类型占内存的大小,而strlen计算的是字符串实际的长度。
  • 数组做sizeof的参数不退化,传递给strlen就退化为指针了

6 C 语言的关键字 static 和 C++ 的关键字 static 有什么区别

在 C 中 static 用来修饰局部静态变量和外部静态变量、函数。而 C++中除了上述功能外,还用来定义类的成员变量和函数。即静态成员和静态成员函数。

「注意」:编程时 static 的记忆性,和全局性的特点可以让在不同时期调用的函数进行通信,传递信息,而 C++的静态成员则可以在多个对象实例间进行通信,传递信息。

7 C 语言的 malloc 和 C++ 中的 new 有什么区别

  • new 、delete 是操作符,可以重载,只能在C++ 中使用。
  • malloc、free 是函数,可以覆盖,C、C++ 中都可以使用。
  • new 可以调用对象的构造函数,对应的delete 调用相应的析构函数。
  • malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数
  • new 、delete 返回的是某种数据类型指针,malloc、free 返回的是void 指针。

「注意」:malloc 申请的内存空间要用free 释放,而new 申请的内存空间要用delete 释放,不要混用。

8 写一个 “标准”宏MIN

#define min(a,b)((a)<=(b)?(a):(b))

9 ++i和i++的区别

++i先自增1,再返回,i++先返回i,再自增1

10 volatile有什么作用

  • 状态寄存器一类的并行设备硬件寄存器。
  • 一个中断服务子程序会访问到的非自动变量。
  • 多线程间被几个任务共享的变量。

「注意」:虽然volatile在嵌入式方面应用比较多,但是在PC软件的多线程中,volatile修饰的临界变量也是非常实用的。

11 一个参数可以既是const又是volatile吗

可以,用const和volatile同时修饰变量,表示这个变量在程序内部是只读的,不能改变的,只在程序外部条件变化下改变,并且编译器不会优化这个变量。每次使用这个变量时,都要小心地去内存读取这个变量的值,而不是去寄存器读取它的备份。

注意:在此一定要注意const的意思,const只是不允许程序中的代码改变某一变量,其在编译期发挥作用,它并没有实际地禁止某段内存的读写特性。

12 a 和&a 有什么区别

&a:其含义就是“变量a的地址”。

*a:用在不同的地方,含义也不一样。

  • 在声明语句中,*a只说明a是一个指针变量,如int *a;
  • 在其他语句中,*a前面没有操作数且a是一个指针时,*a代表指针a指向的地址内存放的数据,如b=*a;
  • *a前面有操作数且a是一个普通变量时,a代表乘以a,如c=ba。

13 用C 编写一个死循环程序

while(1) { } 

「注意」:很多种途径都可实现同一种功能,但是不同的方法时间和空间占用度不同,特别是对于嵌入 式软件,处理器速度比较慢,存储空间较小,所以时间和空间优势是选择各种方法的首要考虑条件。

14 结构体内存对齐问题

请写出以下代码的输出结果:

#include<stdio.h>struct S1{    int i:8;    char j:4;    int a:4;    double b;}; struct S2{    int i:8;    char j:4;    double b;    int a:4;}; struct S3{    int i;    char j;    double b;    int a;}; int main(){    printf('%d\n',sizeof(S1));  // 输出8    printf('%d\n',sizeof(S1);  // 输出12    printf('%d\n',sizeof(Test3));  // 输出8    return 0;} sizeof(S1)=16sizeof(S2)=24sizeof(S3)=32

「说明」:结构体作为一种复合数据类型,其构成元素既可以是基本数据类型的变量,也可以是一些复合型类型数据。对此,编译器会自动进行成员变量的对齐以提高运算效率。默认情况下,按自然对齐条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同,向结构体成员中size最大的成员对齐。

许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,而这个k则被称为该数据类型的对齐模数。

15 全局变量和局部变量有什么区别?怎么实现的?操作系统和编译器是怎么知道的?

  • 全局变量是整个程序都可访问的变量,谁都可以访问,生存期在整个程序从运行到结束(在程序结束时所占内存释放);
  • 而局部变量存在于模块(子程序,函数)中,只有所在模块可以访问,其他模块不可直接访问,模块结束(函数调用完毕),局部变量消失,所占据的内存释放。
  • 操作系统和编译器,可能是通过内存分配的位置来知道的,全局变量分配在全局数据段并且在程序开始运行的时候被加载.局部变量则分配在堆栈里面。

16 简述C、C++程序编译的内存分配情况

  • 从静态存储区域分配:

内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。速度快、不容易出错, 因为有系统会善后。例如全局变量,static 变量,常量字符串等。

  • 在栈上分配:

在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释 放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。大小为2M。

  • 从堆上分配:

即动态内存分配。程序在运行的时候用 malloc 或new 申请任意大小的内存,程序员自己负责在何 时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生 堆内碎块。

一个C、C++程序编译时内存分为5 大存储区:堆区、栈区、全局区、文字常量区、程序代码区。

17 简述strcpy、sprintf 与memcpy 的区别

  • 操作对象不同,strcpy 的两个操作对象均为字符串,sprintf 的操作源对象可以是多种数据类型, 目的操作对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
  • 执行效率不同,memcpy 最高,strcpy 次之,sprintf 的效率最低。
  • 实现功能不同,strcpy 主要实现字符串变量间的拷贝,sprintf 主要实现其他数据类型格式到字 符串的转化,memcpy 主要是内存块间的拷贝。

「注意」:strcpy、sprintf 与memcpy 都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来 选择合适的函数实现拷贝功能。

18 请解析((void ()( ) )0)( )的含义

  • void (*0)( ) :是一个返回值为void,参数为空的函数指针0。
  • (void (*)( ))0:把0转变成一个返回值为void,参数为空的函数指针。
  • (void ()( ))0:在上句的基础上加*表示整个是一个返回值为void,无参数,并且起始地址为0的函数的名字。
  • ((void ()( ))0)( ):这就是上句的函数名所对应的函数的调用。

19 C语言的指针和引用和c++的有什么区别?

  • 指针有自己的一块空间,而引用只是一个别名;
  • 使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;
  • 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引 用的修改都会改变引用所指向的对象;
  • 可以有const指针,但是没有const引用;
  • 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能 被改变;
  • 指针可以有多级指针(**p),而引用止于一级;
  • 指针和引用使用++运算符的意义不一样;
  • 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。

20 typedef 和define 有什么区别

  • 用法不同:typedef 用来定义一种数据类型的别名,增强程序的可读性。define 主要用来定义 常量,以及书写复杂使用频繁的宏。
  • 执行时间不同:typedef 是编译过程的一部分,有类型检查的功能。define 是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
  • 作用域不同:typedef 有作用域限定。define 不受作用域约束,只要是在define 声明后的引用 都是正确的。
  • 对指针的操作不同:typedef 和define 定义的指针时有很大的区别。

「注意」:typedef 定义是语句,因为句尾要加上分号。而define 不是语句,千万不能在句尾加分号。

21 指针常量与常量指针区别

指针常量是指定义了一个指针,这个指针的值只能在定义时初始化,其他地方不能改变。常量指针 是指定义了一个指针,这个指针指向一个只读的对象,不能通过常量指针来改变这个对象的值。指针常量强调的是指针的不可改变性,而常量指针强调的是指针对其所指对象的不可改变性。

「注意」:无论是指针常量还是常量指针,其最大的用途就是作为函数的形式参数,保证实参在被调用 函数中的不可改变特性。

22 简述队列和栈的异同

队列和栈都是线性存储结构,但是两者的插入和删除数据的操作不同,队列是“先进先出”,栈是 “后进先出”。

「注意」:区别栈区和堆区。堆区的存取是“顺序随意”,而栈区是“后进先出”。栈由编译器自动分 配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。堆一般由程序员 分配释放, 若程序员不释放,程序结束时可能由OS 回收。分配方式类似于链表。它与本题中的堆和栈是两回事。堆栈只是一种数据结构,而堆区和栈区是程序的不同内存存储区域。

23 设置地址为0x67a9 的整型变量的值为0xaa66

int *ptr; ptr = (int *)0x67a9; *ptr = 0xaa66; 

「注意」:这道题就是强制类型转换的典型例子,无论在什么平台地址长度和整型数据的长度是一样的, 即一个整型数据可以强制转换成地址指针类型,只要有意义即可。

24 编码实现字符串转化为数字

编码实现函数atoi(),设计一个程序,把一个字符串转化为一个整型数值。例如数字:“5486321 ”, 转化成字符:5486321。

int myAtoi(const char * str) {   int num = 0; //保存转换后的数值    int isNegative = 0; //记录字符串中是否有负号     int n =0;    char *p = str;    if(p == NULL) //判断指针的合法性    {       return -1;    }    while(*p++ != '\0') //计算数字符串度    {       n++;    }    p = str;    if(p[0] == '-') //判断数组是否有负号    {       isNegative = 1;    }     char temp = '0';    for(int i = 0 ; i < n; i++)    {       char temp = *p++;        if(temp > '9' ||temp < '0') //滤除非数字字符       {          continue;       }       if(num !=0 || temp != '0') //滤除字符串开始的0 字符       {          temp -= 0x30; //将数字字符转换为数值           num += temp *int( pow(10 , n - 1 -i) );        }    }    if(isNegative) //如果字符串中有负号,将数值取反    {        return (0 - num);    }    else    {       return num; //返回转换后的数值    } } 

25 C语言的结构体和C++的有什么区别

  • C语言的结构体是不能有函数成员的,而C++的类可以有。
  • C语言的结构体中数据成员是没有private、public和protected访问限定的。而C++的类的成员有这些访问限定。
  • C语言的结构体是没有继承关系的,而C++的类却有丰富的继承关系。

「注意」:虽然C的结构体和C++的类有很大的相似度,但是类是实现面向对象的基础。而结构体只可以简单地理解为类的前身。

26 简述指针常量与常量指针的区别

  • 指针常量是指定义了一个指针,这个指针的值只能在定义时初始化,其他地方不能改变。常量指针是指定义了一个指针,这个指针指向一个只读的对象,不能通过常量指针来改变这个对象的值。
  • 指针常量强调的是指针的不可改变性,而常量指针强调的是指针对其所指对象的不可改变性。

「注意」:无论是指针常量还是常量指针,其最大的用途就是作为函数的形式参数,保证实参在被调用函数中的不可改变特性。

27 如何避免“野指针”

  • 指针变量声明时没有被初始化。解决办法:指针声明时初始化,可以是具体的地址值,也可让它指向NULL。
  • 指针p被free或者delete之后,没有置为NULL。解决办法:指针指向的内存空间被释放后指针应该指向NULL。
  • 指针操作超越了变量的作用范围。解决办法:在变量的作用域结束前释放掉变量的地址空间并且让指针指向NULL。

28 句柄和指针的区别和联系是什么?

句柄和指针其实是两个截然不同的概念。Windows系统用句柄标记系统资源,隐藏系统的信息。你只要知道有这个东西,然后去调用就行了,它是个32it的uint。指针则标记某个物理内存地址,两者是不同的概念。

29 new/delete与malloc/free的区别是什么

  • new能自动计算需要分配的内存空间,而malloc需要手工计算字节数。
int *p = new int[2];int *q = (int *)malloc(2*sizeof(int));
  • new与delete直接带具体类型的指针,malloc和free返回void类型的指针。
  • new类型是安全的,而malloc不是。例如int *p = new float[2];就会报错;而int p = malloc(2sizeof(int))编译时编译器就无法指出错误来。
  • new一般分为两步:new操作和构造。new操作对应与malloc,但new操作可以重载,可以自定义内存分配策略,不做内存分配,甚至分配到非内存设备上,而malloc不行。
  • new调用构造函数,malloc不能;delete调用析构函数,而free不能。
  • malloc/free需要库文件stdlib.h的支持,new/delete则不需要!

「注意」:delete和free被调用后,内存不会立即回收,指针也不会指向空,delete或free仅仅是告诉操作系统,这一块内存被释放了,可以用作其他用途。但是由于没有重新对这块内存进行写操作,所以内存中的变量数值并没有发生变化,出现野指针的情况。因此,释放完内存后,应该讲该指针指向NULL。

30 说一说extern“C”

extern 'C'的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern 'C'后,会指示编译器这部分代码按C语言(而不是C++)的方式进行编译。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。

这个功能十分有用处,因为在C++出现以前,很多代码都是C语言写的,而且很底层的库也是C语言写的,为了更好的支持原来的C代码和已经写好的C语言库,需要在C++中尽可能的支持C,而extern 'C'就是其中的一个策略。

  • C++代码调用C语言代码
  • 在C++的头文件中使用
  • 在多个人协同开发时,可能有的人比较擅长C语言,而有的人擅长C++,这样的情况下也会有用到

31 请你来说一下C++中struct和class的区别

在C++中,class和struct做类型定义是只有两点区别:

  • 默认继承权限不同,class继承默认是private继承,而struct默认是public继承
  • class还可用于定义模板参数,像typename,但是关键字struct不能同于定义模板参数 C++保留struct关键字,原因
  • 保证与C语言的向下兼容性,C++必须提供一个struct
  • C++中的struct定义必须百分百地保证与C语言中的struct的向下兼容性,把C++中的最基本的对象单元规定为class而不是struct,就是为了避免各种兼容性要求的限制
  • 对struct定义的扩展使C语言的代码能够更容易的被移植到C++中

32 C++类内可以定义引用数据成员吗?

可以,必须通过成员函数初始化列表初始化。

33 C++中类成员的访问权限

C++通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符。在类的内部(定义类的代码内部),无论成员被声明为 public、protected 还是 private,都是可以互相访问的,没有访问权限的限制。在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访问 private、protected 属性的成员

34 什么是右值引用,跟左值又有什么区别?

左值和右值的概念:

  • 左值:能取地址,或者具名对象,表达式结束后依然存在的持久对象;
  • 右值:不能取地址,匿名对象,表达式结束后就不再存在的临时对象;区别:
  • 左值能赋值,右值不能;
  • 左值可变,右值不能(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变);

35 面向对象的三大特征

  • 封装性:将客观事物抽象成类,每个类对自身的数据和方法实行 protection (private , protected , public )。
  • 继承性:广义的继承有三种实现形式:实现继承(使用基类的属性和方法而无需额外编码的能力)、可 视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。
  • 多态性:是将父类对象设置成为和一个或更多它的子对象相等的技术。用子类对象给父类对象赋值 之后,父类对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。

36 说一说c++中四种cast转换

C++中四种类型转换是:static_cast, dynamic_cast, const_cast, reinterpret_cast

1、const_cast

  • 用于将const变量转为非const

2、static_cast

  • 用于各种隐式转换,比如非const转const,void*转指针等, static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知;

3、dynamic_cast

用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的***对于指针返回NULL,对于引用抛异常***。要深入了解内部转换的原理。

  • 向上转换:指的是子类向基类的转换
  • 向下转换:指的是基类向子类的转换

它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。

4、reinterpret_cast

  • 几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;

5、为什么不使用C的强制转换?

  • C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错。

37 C++的空类有哪些成员函数

  • 缺省构造函数。
  • 缺省拷贝构造函数。
  • 省析构函数。
  • 赋值运算符。
  • 取址运算符。
  • 取址运算符 const 。

「注意」:有些书上只是简单的介绍了前四个函数。没有提及后面这两个函数。但后面这两个函数也是 空类的默认函数。另外需要注意的是,只有当实际使用这些函数的时候,编译器才会去定义它们。

38 对c++中的smart pointer四个智能指针:shared_ptr,unique_ptr,weak_ptr,auto_ptr的理解

C++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被11弃用。

智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

  • auto_ptr(c++98的方案,cpp11已经抛弃)

采用所有权模式。

auto_ptr< string> p1 (new string ('I reigned lonely as a cloud.”));auto_ptr<string> p2;p2 = p1; //auto_ptr不会报错.

此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!

  • unique_ptr(替换auto_ptr)

unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。

采用所有权模式。

unique_ptr<string> p3 (new string ('auto'));   //#4unique_ptr<string> p4;                       //#5p4 = p3;//此时会报错!!

编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。

另外unique_ptr还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做,比如:

unique_ptr<string> pu1(new string ('hello world'));unique_ptr<string> pu2;pu2 = pu1;                                      // #1 not allowedunique_ptr<string> pu3;pu3 = unique_ptr<string>(new string ('You'));   // #2 allowed

其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。

「注意」:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。例如:

unique_ptr<string> ps1, ps2;ps1 = demo('hello');ps2 = move(ps1);ps1 = demo('alexia');cout << *ps2 << *ps1 << endl;
  • shared_ptr

shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。

shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。

成员函数:

use_count 返回引用计数的个数

unique 返回是否是独占所有权( use_count 为 1)

swap 交换两个 shared_ptr 对象(即交换所拥有的对象)

reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少

get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptrsp(new int(1)); sp 与 sp.get()是等价的

  • weak_ptr

weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

class B;class A{public:shared_ptr<B> pb_;~A(){     cout<<'A delete';}};class B{public:shared_ptr<A> pa_;~B(){    cout<<'B delete';}};void fun(){    shared_ptr<B> pb(new B());    shared_ptr<A> pa(new A());    pb->pa_ = pa;    pa->pb_ = pb;    cout<<pb.use_count()<<endl;    cout<<pa.use_count()<<endl;}int main(){    fun();    return 0;}

可以看到fun函数中pa ,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A B的析构函数没有被调用),如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_; 改为weak_ptr pb_; 运行结果如下,这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减一,同时pa析构时使A的计数减一,那么A的计数为0,A得到释放。

「注意」:不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(); 英文pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:shared_ptr p = pa->pb_.lock(); p->print();

(0)

相关推荐