C++知识点

本文详细解析了C++编程中的核心概念,包括变量声明与定义的区别、指针与引用的不同之处、宏定义与函数的区别等内容,并深入探讨了面向对象编程的特点,如封装、继承和多态等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 变量声明和定义区别?
    o 声明仅仅是把变量的声明的位置及类型提供给编译器,并不分配内存空间;定义要在定义的地方为其分配存储空间。
    o 相同变量可以再多处声明(外部变量extern),但只能在一处定义。
  2. “零值比较”?
    o bool类型:if(flag)
    o int类型:if(flag == 0)
    o 指针类型:if(flag == null)
    o float类型:if((flag >= -0.000001) && (flag <= 0. 000001))
  3. strlen和sizeof区别?
    o sizeof是运算符,并不是函数,结果在编译时得到而非运行中获得;strlen是字符处理的库函数。
    o sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化);strlen的参数只能是字符指针且结尾是’\0’的字符串。
    o 因为sizeof值在编译时确定,所以不能用来得到动态分配(运行时分配)存储空间的大小。
  4. 同一不同对象可以互相赋值吗?
    o 可以,但含有指针成员时需要注意。
    o 对比类的对象赋值时深拷贝和浅拷贝。
  5. 结构体内存对齐问题?
    o 结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。
    o 未特殊说明时,按结构体中size最大的成员对齐(若有double成员),按8字节对齐。
  6. static作用是什么?在C和C++中有何区别?
    o static可以修饰局部变量(静态局部变量)、全局变量(静态全局变量)和函数,被修饰的变量存储位置在静态区。对于静态局部变量,相对于一般局部变量其生命周期长,直到程序运行结束而非函数调用结束,且只在第一次被调用时定义;对于静态全局变量,相对于全局变量其可见范围被缩小,只能在本文件中可见;修饰函数时作用和修饰全局变量相同,都是为了限定访问域。
    o C++的static除了上述两种用途,还可以修饰类成员(静态成员变量和静态成员函数),静态成员变量和静态成员函数不属于任何一个对象,是所有类实例所共有。
    o static的数据记忆性可以满足函数在不同调用期的通信,也可以满足同一个类的多个实例间的通信。
    o 未初始化时,static变量默认值为0。
  7. 结构体和类的区别?
    o 结构体的默认限定符是public;类是private。
    • C++中结构体也可以继承。
  8. malloc和new的区别?
    o malloc和free是标准库函数,支持覆盖;new和delete是运算符,并且支持重载。
    o malloc仅仅分配内存空间,free仅仅回收空间,不具备调用构造函数和析构函数功能,用malloc分配空间存储类的对象存在风险;new和delete除了分配回收功能外,还会调用构造函数和析构函数。
    o malloc和free返回的是void类型指针(必须进行类型转换),new和delete返回的是具体类型指针。
  9. 指针和引用区别? - 引用只是别名,不占用具体存储空间,只有声明没有定义;指针是具体变量,需要占用存储空间。
    o 引用在声明时必须初始化为另一变量,一旦出现必须为typename refname &varname形式;指针声明和定义可以分开,可以先只声明指针变量而不初始化,等用到时再指向具体变量。
    o 引用一旦初始化之后就不可以再改变(变量可以被引用为多次,但引用只能作为一个变量引用);指针变量可以重新指向别的变量。
    o 不存在指向空值的引用,必须有具体实体;但是存在指向空值的指针。
  10. 宏定义和函数有何区别?
    o 宏在编译时完成替换,之后被替换的文本参与编译,相当于直接插入了代码,运行时不存在函数调用,执行起来更快;函数调用在运行时需要跳转到具体调用函数。
    o 宏函数属于在结构中插入代码,没有返回值;函数调用具有返回值。
    o 宏函数参数没有类型,不进行类型检查;函数参数具有类型,需要检查类型。
    o 宏函数不要在最后加分号。
  11. 宏定义和const区别?
    o 宏替换发生在编译阶段之前,属于文本插入替换;const作用发生于编译过程中。
    o 宏不检查类型;const会检查数据类型。
    o 宏定义的数据没有分配内存空间,只是插入替换掉;const定义的变量只是值不能改变,但要分配内存空间。
  12. 宏定义和typedef区别?
    o 宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。
    o 宏替换发生在编译阶段之前,属于文本插入替换;typedef是编译的一部分。
    o 宏不检查类型;typedef会检查数据类型。
    o 宏不是语句,不在在最后加分号;typedef是语句,要加分号标识结束。
    o 注意对指针的操作,typedef char * p_char和#define p_char char *区别巨大。
  13. 宏定义和内联函数(inline)区别?
    o 在使用时,宏只做简单字符串替换(编译前)。而内联函数可以进行参数类型检查(编译时),且具有返回值。
    o 内联函数本身是函数,强调函数特性,具有重载等功能。
    o 内联函数可以作为某个类的成员函数,这样可以使用类的保护成员和私有成员。而当一个表达式涉及到类保护成员或私有成员时,宏就不能实现了。
  14. 条件编译#ifdef, #else, #endif作用?
    o 可以通过加#define,并通过#ifdef来判断,将某些具体模块包括进要编译的内容。
    o 用于子程序前加#define DEBUG用于程序调试。
    o 应对硬件的设置(机器类型等)。
    o 条件编译功能if也可实现,但条件编译可以减少被编译语句,从而减少目标程序大小。
  15. 区别以下几种变量?
  16. const int a;
  17. int const a;
  18. const int *a;
  19. int *const a;
    o int const a和const int a均表示定义常量类型a。
    o const int a,其中a为指向int型变量的指针,const在 左侧,表示a指向不可变常量。(看成const (*a),对引用加const)
    o int *const a,依旧是指针类型,表示a为指向整型数据的常指针。(看成const(a),对指针const)
  20. volatile有什么作用?
    o volatile定义变量的值是易变的,每次用到这个变量的值的时候都要去重新读取这个变量的值,而不是读寄存器内的备份。
    o 多线程中被几个任务共享的变量需要定义为volatile类型。
  21. 什么是常引用?
    o 常引用可以理解为常量指针,形式为const typename & refname = varname。
    o 常引用下,原变量值不会被别名所修改。
    o 原变量的值可以通过原名修改。
    o 常引用通常用作只读变量别名或是形参传递。
  22. 区别以下指针类型?
  23. int *p[10]
  24. int (*p)[10]
  25. int *p(int)
  26. int (*p)(int)
    o int *p[10]表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。
    o int (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
    o int p(int)是函数声明,函数名是p,参数是int类型的,返回值是int 类型的。
    o int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。
  27. 常量指针和指针常量区别?
    o 常量指针是一个指针,读成常量的指针,指向一个只读变量。如int const *p或const int *p。
    o 指针常量是一个不能给改变指向的指针。如int *const p。
  28. a和&a有什么区别?
  29. 假设数组int a[10];
  30. int (*p)[10] = &a;
    o a是数组名,是数组首元素地址,+1表示地址值加上一个int类型的大小,如果a的值是0x00000001,加1操作后变为0x00000005。*(a + 1) = a[1]。
    o &a是数组的指针,其类型为int (*)[10](就是前面提到的数组指针),其加1时,系统会认为是数组首地址加上整个数组的偏移(10个int型变量),值为数组a尾元素后一个元素的地址。
    o 若(int )p ,此时输出 *p时,其值为a[0]的值,因为被转为int 类型,解引用时按照int类型大小来读取。
  31. 数组名和指针(这里为指向数组首元素的指针)区别?
    o 二者均可通过增减偏移量来访问数组中的元素。
    o 数组名不是真正意义上的指针,可以理解为常指针,所以数组名没有自增、自减等操作。
    o 当数组名当做形参传递给调用函数后,就失去了原有特性,退化成一般指针,多了自增、自减操作,但sizeof运算符不能再得到原数组的大小了。
  32. 野指针是什么?
    o 也叫空悬指针,不是指向null的指针,是指向垃圾内存的指针。
    o 产生原因及解决办法:
     指针变量未及时初始化 => 定义指针变量及时初始化,要么置空。
     指针free或delete之后没有及时置空 => 释放操作后立即置空。
  33. 堆和栈的区别?
    o 申请方式不同。
     栈由系统自动分配。
     堆由程序员手动分配。
    o 申请大小限制不同。
     栈顶和栈底是之前预设好的,大小固定,可以通过ulimit -a查看,由ulimit -s修改。
     堆向高地址扩展,是不连续的内存区域,大小可以灵活调整。
    o 申请效率不同。
     栈由系统分配,速度快,不会有碎片。
     堆由程序员分配,速度慢,且会有碎片。
  34. delete和delete[]区别?
    o delete只会调用一次析构函数。
    o delete[]会调用数组中每个元素的析构函数。
    面向对象基础
    能够准确理解下面这些问题是从C程序员向C++程序员进阶的基础。当然了,这只是一部分。
  35. 面向对象三大特性?
    o 封装性:数据和代码捆绑在一起,避免外界干扰和不确定性访问。
    o 继承性:让某种类型对象获得另一个类型对象的属性和方法。
    o 多态性:同一事物表现出不同事物的能力,即向不同对象发送同一消息,不同的对象在接收时会产生不同的行为(重载实现编译时多态,虚函数实现运行时多态)。
  36. public/protected/private的区别?
    o public的变量和函数在类的内部外部都可以访问。
    o protected的变量和函数只能在类的内部和其派生类中访问。
    o private修饰的元素只能在类内访问。
  37. 对象存储空间?
    o 非静态成员的数据类型大小之和。
    • 编译器加入的额外成员变量(如指向虚函数表的指针)。
    • 为了边缘对齐优化加入的panding。
  38. C++空类有哪些成员函数?
    o 首先,空类大小为1字节。
    o 默认函数有:
     构造函数
     析构函数
     拷贝构造函数
     赋值运算符
  39. 构造函数能否为虚函数,析构函数呢?
    o 析构函数:
     析构函数可以为虚函数,并且一般情况下基类析构函数要定义为虚函数。
     只有在基类析构函数定义为虚函数时,调用操作符delete销毁指向对象的基类指针时,才能准确调用派生类的析构函数(从该级向上按序调用虚函数),才能准确销毁数据。
     析构函数可以是纯虚函数,含有纯虚函数的类是抽象类,此时不能被实例化。但派生类中可以根据自身需求重新改写基类中的纯虚函数。
    o 构造函数:
     构造函数不能定义为虚函数,不仅如此,构造函数中还不能调用虚函数。因为那样实际执行的是父类对应的函数,因为自己还没有构造好(构造顺序先基类再派生类)。
  40. 构造函数调用顺序,析构函数呢?
    o 基类的构造函数:如果有多个基类,先调用纵向上最上层基类构造函数,如果横向继承了多个类,调用顺序为派生表从左到右顺序。
    o 成员类对象的构造函数:如果类的变量中包含其他类(类的组合),需要在调用本类构造函数前先调用成员类对象的构造函数,调用顺序遵照在类中被声明的顺序。
    o 派生类的构造函数。
    o 析构函数与之相反。
  41. 拷贝构造函数中深拷贝和浅拷贝区别?
    o 深拷贝时,当被拷贝对象存在动态分配的存储空间时,需要先动态申请一块存储空间,然后逐字节拷贝内容。
    o 浅拷贝仅仅是拷贝指针字面值。
    o 当使用浅拷贝时,如果原来的对象调用析构函数释放掉指针所指向的数据,则会产生空悬指针。因为所指向的内存空间已经被释放了。
  42. 拷贝构造函数和赋值运算符重载的区别?
    o 拷贝构造函数是函数,赋值运算符是运算符重载。
    o 拷贝构造函数会生成新的类对象,赋值运算符不能。
    o 拷贝构造函数是直接构造一个新的类对象,所以在初始化对象前不需要检查源对象和新建对象是否相同;赋值运算符需要上述操作并提供两套不同的复制策略,另外赋值运算符中如果原来的对象有内存分配则需要先把内存释放掉。
    o 形参传递是调用拷贝构造函数(调用的被赋值对象的拷贝构造函数),但并不是所有出现”=”的地方都是使用赋值运算符,如下:
    o Student s;
    o Student s1 = s; // 调用拷贝构造函数
    o Student s2;
    o s2 = s; // 赋值运算符操作
    注:类中有指针变量时要重写析构函数、拷贝构造函数和赋值运算符
  43. 虚函数和纯虚函数区别?
    o 虚函数是为了实现动态编联产生的,目的是通过基类类型的指针指向不同对象时,自动调用相应的、和基类同名的函数(使用同一种调用形式,既能调用派生类又能调用基类的同名函数)。虚函数需要在基类中加上virtual修饰符修饰,因为virtual会被隐式继承,所以子类中相同函数都是虚函数。当一个成员函数被声明为虚函数之后,其派生类中同名函数自动成为虚函数,在派生类中重新定义此函数时要求函数名、返回值类型、参数个数和类型全部与基类函数相同。
    o 纯虚函数只是相当于一个接口名,但含有纯虚函数的类不能够实例化。
  44. 覆盖、重载和隐藏的区别?
    o 覆盖是派生类中重新定义的函数,其函数名、参数列表(个数、类型和顺序)、返回值类型和父类完全相同,只有函数体有区别。派生类虽然继承了基类的同名函数,但用派生类对象调用该函数时会根据对象类型调用相应的函数。覆盖只能发生在类的成员函数中。
    o 隐藏是指派生类函数屏蔽了与其同名的函数,这里仅要求基类和派生类函数同名即可。其他状态同覆盖。可以说隐藏比覆盖涵盖的范围更宽泛,毕竟参数不加限定。
    o 重载是具有相同函数名但参数列表不同(个数、类型或顺序)的两个函数(不关心返回值),当调用函数时根据传递的参数列表来确定具体调用哪个函数。重载可以是同一个类的成员函数也可以是类外函数。
  45. 在main执行之前执行的代码可能是什么?
    o 全局对象的构造函数。
  46. 哪几种情况必须用到初始化成员列表?
    o 初始化一个const成员。
    o 初始化一个reference成员。
    o 调用一个基类的构造函数,而该函数有一组参数。
    o 调用一个数据成员对象的构造函数,而该函数有一组参数。
  47. 什么是虚指针?
    o 虚指针或虚函数指针是虚函数的实现细节。
    o 虚指针指向虚表结构。
  48. 重载和函数模板的区别?
    o 重载需要多个函数,这些函数彼此之间函数名相同,但参数列表中参数数量和类型不同。在区分各个重载函数时我们并不关心函数体。
    o 模板函数是一个通用函数,函数的类型和形参不直接指定而用虚拟类型来代表。但只适用于参个数相同而类型不同的函数。
  49. this指针是什么?
    o this指针是类的指针,指向对象的首地址。
    o this指针只能在成员函数中使用,在全局函数、静态成员函数中都不能用this。
    o this指针只有在成员函数中才有定义,且存储位置会因编译器不同有不同存储位置。
  50. 类模板是什么?
    o 用于解决多个功能相同、数据类型不同的类需要重复定义的问题。
    o 在建立类时候使用template及任意类型标识符T,之后在建立类对象时,会指定实际的类型,这样才会是一个实际的对象。
    o 类模板是对一批仅数据成员类型不同的类的抽象,只要为这一批类创建一个类模板,即给出一套程序代码,就可以用来生成具体的类。
  51. 构造函数和析构函数调用时机?
    o 全局范围中的对象:构造函数在所有函数调用之前执行,在主函数执行完调用析构函数。
    o 局部自动对象:建立对象时调用构造函数,函数结束时调用析构函数。
    o 动态分配的对象:建立对象时调用构造函数,调用释放时调用析构函数。
    o 静态局部变量对象:建立时调用一次构造函数,主函数结束时调用析构函数。

标准模板库
STL内容虽然看起来很多,单独成书都不是问题(《STL源码剖析》),但从实际使用状况来看,我认为只需要知道以下几点就可以了:
• 怎么用?
各种STL基本的增删改查怎么使用。每种容器都提供了很多操作,但实际增删改查我们通常只需要掌握透彻一种方式即可。有些功能只是出于通用性考虑才存在的,但对于相应的STL这些操作完全可以忽略。所以我对STL使用的看法是,不需要花太多时间去了解所有功能,只要掌握最基本的即可,要把精力放在对需求的了解并选择适合的数据结构。
• 怎么实现?
本身STL就是封装了我们常用的数据结构,所以最先需要了解每种数据结构的特性。而且了解实现方式对我们能够准确、高效使用STL打下了基础。
• 如何避免错误?
在第二阶段了解了STL的实现之后,我们已经可以很清楚地知道他们底层使用的是什么数据结构以及该数据结构做什么操作比较高效。但还有一点需要注意的就是怎么才能用对他们,避免一些未知的错误,比如迭代器失效问题。
string
vector
用法:
定义:
vector vec;

插入元素:
    vec.push_back(element);
    vec.insert(iterator, element);

删除元素:
    vec.pop_back();
    vec.erase(iterator);

修改元素:
    vec[position] = element;

遍历容器:
    for(auto it = vec.begin(); it != vec.end(); ++it) {......}

其他:
    vec.empty();    //判断是否空
    vec.size();    // 实际元素
    vec.capacity();    // 容器容量
    vec.begin();    // 获得首迭代器
    vec.end();    // 获得尾迭代器
    vec.clear();    // 清空

实现:
模拟Vector实现
• 线性表,数组实现。
o 支持随机访问。
o 插入删除操作需要大量移动数据。
• 需要连续的物理存储空间。
• 每当大小不够时,重新分配内存(*2),并复制原内容。
错误避免:
迭代器失效
• 插入元素
o 尾后插入:size < capacity时,首迭代器不失效尾迭代实现(未重新分配空间),size == capacity时,所有迭代器均失效(需要重新分配空间)。
o 中间插入:size < capacity时,首迭代器不失效但插入元素之后所有迭代器失效,size == capacity时,所有迭代器均失效。
• 删除元素
o 尾后删除:只有尾迭代失效。
o 中间删除:删除位置之后所有迭代失效。
map
用法:
定义:
map

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值