C++ 概念笔记(三)-适用于考研复试

此篇博客主要总结c++部分概念,包括不限于c++11,流,异常等

基础概念参考:基础概念笔记

面向对象参考:面向对象笔记

STL参考:

持续更新

 

目录

1.结构体对齐

2.基类的虚函数表存放在内存的什么区,虚表指针vptr的初始化时间

3.宏定义和typedef区别

4.strlen和sizeof区别  

5.迭代器失效

6.volatile关键字

7.final和override关键字

8.explicit关键字

9.extern"C"

10.类和函数模板特例化

11.C++中的重载、重写(覆盖)和隐藏的区别

12.内联函数和宏定义的区别

13.C++11有哪些新特性

14.auto、decltype和decltype(auto)的用法

15.大小端存储

16.C++的异常处理的方法

17.static 类 plus

18.值传递、指针传递、引用传递的区别和效率

19.内存池

20.类内存分布

21.智能指针

22.移动构造函数

23.this 指针

24.构造函数、拷贝构造函数和赋值操作符

25.怎样判断两个浮点数是否相等?

26.ifdef endif

27.在类的析构函数中调用delete this

28.命令行参数

29.为什么拷贝构造函数必须传引用不能传值?

30.静态函数能定义为虚函数吗

31.C++左值引用和右值引用



1.结构体对齐

结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。

未特殊说明时,按结构体中size最大的成员对齐(若有double成员,按8字节对齐。)

2.基类的虚函数表存放在内存的什么区,虚表指针vptr的初始化时间

虚函数表是全局共享的元素,即全局仅有一个,在编译时就构造完成 ,虚函数表类似于类中静态成员变量,存在全局数据区。

虚表指针在实例化对象时进行初始化,并且存在对象内存布局的最前面。

3.宏定义和typedef区别

 宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。

 宏替换发生在编译阶段之前,属于文本插入替换;typedef是编译的一部分。

宏不检查类型;typedef会检查数据类型。

宏不是语句,不在在最后加分号;typedef是语句,要加分号标识结束。

注意对指针的操作,typedef char * p_char和#defifine p_char char *区别(*符号优先级)

4.strlensizeof区别  

sizeof是运算符,并不是函数,结果在编译时得到而非运行中获得;strlen是字符处理的库函数。

sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化);strlen的参数只能是字符指针

且结尾是'\0'的字符串。

 int main(int argc, char const *argv[]){
      
      const char* str = "name";
      sizeof(str); // 取的是指针str的长度,是8
      strlen(str); // 取的是这个字符串的长度,不包含结尾的 \0。大小是4
      return 0;
 }

5.迭代器失效

尾后删除:只有尾迭代失效。

中间删除:删除位置之后所有迭代失效。

deque vector 的情况类似, 而list双向链表每一个节点内存不连续, 删除节点仅当前迭代器失效,erase返回下一个有效迭代器;

map/set等关联容器底层是红黑树删除节点不会影响其他节点的迭代器, 使用递增方法获取下一个迭代 器 mmp.erase(iter++);

6.volatile关键字

volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据,不使用寄存器中的值。

多线程下的volatile:

当两个线程都要用到某一个变量且该变量的值会被改变时,应该 用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。

7.finaloverride关键字

当在父类中使用了虚函数时候,你可能需要在某个子类中对这个虚函数进行重写,以下方法都可以:
class A {
    virtual void foo();
}
class B : public A {
    void foo(); //OK
    virtual void foo(); // OK
    void foo() override; //OK
}

override显式指定了子类的这个虚函数是重写的父类的,如果你名字不小心打错了的话,编译器是不会编译通过。

 

final :当不希望某个类被继承,或不希望某个虚函数被重写,可以在类名和虚函数后添加fifinal关键字

class Base
{
    virtual void foo();
};
class A : public Base
{
    void foo() final; // foo 被override并且是最后一个override,在其子类中不可以重写
};
class B final : A // 指明B是不可以被继承的
{
    void foo() override; // Error: 在A中已经被final了
};
class C : B // Error: B is final
{
};

 

8.explicit关键字

 C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的


class CxString  // 使用关键字explicit的类声明, 显示转换  
{  
public:   
    int _size;  
    explicit CxString(int size)  
    {  
        _size = size;  
        
    }  
    CxString(const char *p)  
    {  
        
    }  
};  
  
    // 下面是调用:  
  
    CxString string1(24);     // 这样是OK的  
    CxString string2 = 10;    // 这样是不行的, 因为explicit关键字取消了隐式转换 
    CxString string4("aaaa"); // 这样是OK的  
    CxString string5 = "bbb"; // 这样也是OK的

 

9.extern"C"

在程序中加上extern "C"后,相当于告诉编译器这部分 ,代码是C语言写的,因此要按照C语言进行编译,而不是C++

 

1C++代码中调用C语言代码;

2)在C++中的头文件中使用;

3)在多个人协同开发时,可能有人擅长C语言,而有人擅长C++

10.类和函数模板特例化

对单一模板模板声明 template<typename/class T>提供的一个特殊实例,它将一个或多个模板参数绑定到特定的类型或值上。

使用关键字template后跟一个空尖括号对<>,表明将原模板的所有模板参数提供实参。

template<typename T>
class Foo
{
    void Bar();
    void Barst(T a)();
};
template<>
void Foo<int>::Bar()
{
    //进行int类型的特例化处理
}

Foo<string> fs;
Foo<int> fi;//使用特例化
fs.Bar();//使用的是普通模板,即Foo<string>::Bar()
fi.Bar();//特例化版本,执行Foo<int>::Bar()

11.C++中的重载、重写(覆盖)和隐藏的区别

重载是指在同一范围定义中的同名成员函数才存在重载关系。主要特点是函数名相同,参数类型和数目不同。

重写指的是在派生类中覆盖基类中的同名函数,重写就是重写函数体要求基类函数必须是虚函数

重载与重写的区别:

  1. 重写是父类和子类之间的垂直关系,重载是不同函数之间的水平关系
  2. 重写要求参数列表相同,重载则要求参数列表不同,返回值不要求
  3. 重写关系中,调用方法根据对象类型决定,重载根据调用时实参表与形参表的对应关系来选择函数

隐藏指的是某些情况下,派生类中的函数屏蔽了基类中的同名函数:两个函数参数相同,但是基类函数不是虚函数;两个函数参数不同,无论基类函数是不是虚函数,都会被隐藏

12.内联函数和宏定义的区别

  • 内联函数在编译时展开,宏在预编译时展开
  • 内联函数直接嵌入到目标代码中,宏是简单的做文本替换
  • 内联函数有类型检测、语法判断等功能,而宏没有
  • 内联函数是函数,宏不是
  • 宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产生歧义
  • 内联函数代码是被放到符号表中,使用时像宏一样展开,没有调用的开销,效率很高
  • 在使用时,宏只做简单字符串替换(编译前)。而内联函数可以进行参数类型检查(编译时),具有返回值。

13.C++11有哪些新特性

auto,decltype关键字:编译器可以根据初始值自动推导出类型。

nullptr关键字:nullptr是一种特殊类型的字面值,它可以被转换成任意其它的指针类型;

智能指针:C++11新增了std::shared_ptr、std::weak_ptr等类型的智能指针,用于解决内存管理的问题。

基于范围的 for 循环for(auto& i : res){}

初始化列表:使用初始化列表来对类进行初始化

右值引用:基于右值引用可以实现移动语义和完美转发,消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率

*atomic原子操作用于多线程资源互斥操作

新增STL容器array以及tuple

14.autodecltypedecltype(auto)的用法

C++11新标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型

int a = 1, b = 3;
auto c = a + b;// c为int型

//const类型
const int i = 5;
auto j = i; // 变量i是顶层const, 会被忽略, 所以j的类型是int
auto k = &i; // 变量i是一个常量, 对常量取地址是一种底层const, 所以b的类型是const int*
const auto l = i; //如果希望推断出的类型是顶层const的, 那么就需要在auto前面加上cosnt

//引用和指针类型
int x = 2;
int& y = x;
auto z = y; //z是int型不是int& 型
auto& p1 = y; //p1是int&型
auto p2 = &x; //p2是指针类型int

decltype 的作用是选择并返回操作数的数据类型。在此过程中,编译器只是分析表达式并得到它的类型,却不进行实际的计算表达式的值。

decltype(auto) C++14 新增的类型指示符,可以用来声明变量以及指示函数返回类型。在使用时,会将“=” 号左边的表达式替换掉 auto ,再根据 decltype 的语法规则来确定类型。
int e = 4;
const int* f = &e; // f是底层const
decltype(auto) j = f;//j的类型是const int* 并且指向的是e

 

15.大小端存储

大端存储:字数据的高字节存储在低地址中,存储结果同数字本身。

小端存储:字数据的低字节存储在低地址中

16.C++的异常处理的方法

C++中的异常处理机制主要使用trythrowcatch三个关键字

程序的执行流程是先执行try包裹的语句块,如果执行过程中没有异常发生,则不会进入任何catch包裹的语句块,如果发生异常,则使用throw进行异常抛出,再由catch进行捕获,可以使用catch(...)的方式捕获任何异常

有时候,程序员在定义函数的时候知道函数可能发生的异常,可以在函数声明和定义时,指出所能抛出异常的列表

int fun() throw(int,double,A,B,C){...};

17.static 类 plus

  • 在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问
  • static类对象必须要在类外进行初始化,static修饰的变量先于对象存在,所以static修饰的变量要在类外初始化;
  • 由于static修饰的类成员属于类,不属于对象,因此static类成员函数是没有this指针的。正因为没有this指针,所以static类成员函数不能访问非static的类成员,只能访问static修饰的类成员;
  • static成员函数不能被virtual修饰,static成员不属于任何对象或实例,所以加上virtual没有任何实际 意义;静态成员函数没有this指针,虚函数的实现是为每一个对象分配一个vptr指针,而vptr是通过this 指针调用的,所以不能为virtual;

18.值传递、指针传递、引用传递的区别和效率

  • 值传递:有一个形参向函数所属的栈拷贝数据的过程,如果值传递的对象是类对象 或是大的结构
  • 体对象,将耗费一定的时间和空间。(传值)
  • 指针传递:同样有一个形参向函数所属的栈拷贝数据的过程,但拷贝的数据是一个固定为4字节的地 址。(传值,传递的是地址值)
  • 引用传递:针对地址数据拷贝,相当于为该数据所在的地址起了一个 别名。(传地址)
  • 效率上讲,指针传递和引用传递比值传递效率高。

19.内存池

内存池(Memory Pool) 是一种内存分配方式。通常我们习惯直接使用newmalloc 等申请内存,容易造成内存碎片,内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块

 

allocator就是用来分配内存的,最重要的两个函数是allocatedeallocate,就是用来申请内存和回收内存的,allocate包装malloc,deallocate包装free

20.类内存分布

一个类对象的地址就是类所包含的这一片内存空间的首地址,这个首地址也就对应具体某一个成员变量的地址。

空类大小为1

成员函数不占用对象的内存。这是因为所有的函数都是存放在代码区的,不管是全局函数,还是成员函数

所有函数都存放在代码区,静态函数也不例外

21.智能指针

智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源

shared_ptr:允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源。

unique_ptr:一个非空的unique_ptr总是拥有它所指向的资源

weak_ptr弱引用,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是

说,它只引用,不计数。weak_ptr不保证它指向的内存一定是有效的,在使用之前使用函数lock()检查weak_ptr是否为空指针。

 

auto_ptr:主要是为了解决“有异常抛出时发生内存泄漏的问题 。因为发生异常而无法正常释放内存。

22.移动构造函数

我们用对象a初始化对象b,若对象a不再使用了,但对象a的空间还在(在析构之前),为什么我们不能直接使用a的空间呢?这样就避免了新的空间的分配,大大降低了构造的成本。

避免浅拷贝:将第一个指针(比如a->value)置为NULL,所以析构a的时候并不会回收a->value指向的空间

移动构造函数的参数和拷贝构造函数不同,拷贝构造函数的参数是一个左值引用,但是移动构造函数的初值是一个右值引用。只用用一个右值,或者将亡值初始化另一个对象的时候,才会调用移动构造函数。而那个move 语句,就是将一个左值变成一个将亡值。

23.this 指针

this 只能在成员函数中使用,全局函数、静态函数都不能使用 this 。实际上, 成员函数默认第一个
参数 T * const this
class A{
public:
 int func(int p){}
};

其中,func的原型在编译器看来应该是: int func(A * const this,int p);

this在成员函数的开始执行前构造,在成员的执行结束后清除,this指针会因编译器不同而有不同的放置位置。

24.构造函数、拷贝构造函数和赋值操作符

#include <iostream>
using namespace std;
class A {
public:
 A()
 {
 cout << "我是构造函数" << endl;
 }
 A(const A& a)
 {
 cout << "我是拷贝构造函数" << endl;
 }
 A& operator = (A& a)
 {
 cout << "我是赋值操作符" << endl;
 return *this;
 }
 ~A() {};
};
  • 拷贝构造函数是函数,赋值运算符是运算符重载。
  • 拷贝构造函数会生成新的类对象,赋值运算符只能赋值于已经存在的对象。

25.怎样判断两个浮点数是否相等?

对两个浮点数判断大小和是否相等不能直接用==来判断,会出错!明明相等的两个数比较反而是不相等!对于两个浮点数比较只能通过相减并与预先设定的精度比较,记得要取绝对值!浮点数与0的比较也应该注意。与浮点数的表示方式有关。

for循环里面退出语句也不能用浮点型

26.ifdef endif

源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”

\#ifdef 标识符
程序段1
\#else
程序段2
\#endif
当标识符已经被定义过 ( 一般是用 #defifine 命令定义 ) ,则对程序段 1 进行编译,否则编译程
序段 2 。 其中#else 部分也可以没有
 

27.在类的析构函数中调用delete this

会导致堆栈溢出。原因很简单,delete的本质是为将被释放的内存调用一个或多个析构函数,然后,释放内存”。显然,delete this会去调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃。

28.命令行参数

int main(int argc, char *argv[])

 

参数的含义是程序在命令行下运行的时候,需要输入argc 个参数,每个参数是以char 类型输入的,依次存在数组里面,数组是 argv[],所有的参数在指针char * 指向的内存中

29.为什么拷贝构造函数必须传引用不能传值?

拷贝构造函数的作用就是用来复制对象的,若传值则还需调用拷贝构造函数完成参数的拷贝,形成循环。
 

30.静态函数能定义为虚函数吗

static成员不属于任何类对象或类实例,所以即使给此函数加上virutal也是没有任何意义的。

静态成员函数没有this指针。虚函数依靠vptrvtable来处理。vptr是一个指针,在类的构造函数中创建生成,并且只能用this指针来访问它,因为它是类的一个成员,并且vptr指向保存虚函数地址的vtable.对于静态成员函数,它没有this指针,所以无法访问vptr

内联、构造、友元、普通函数都不能是虚函数

31.C++左值引用和右值引用

  • C++11正是通过引入右值引用来优化性能,具体来说是通过移动语义来避免无谓拷贝的问题
  • C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)
  • 无论是声明一个左值引用还是右值引用,都必须立即进行初始化。
  • 左值引用:传统的C++中引用被称为左值引用
  • 右值引用:C++11中增加了右值引用,右值引用关联到右值时,右值被存储到特定位置,右值引用指向 该特定位置,也就是说,右值虽然无法获取地址,但是右值引用是可以获取地址的,该地址表示临时对象的存储位置
int main() {
 
 int a = 10;
 int& b = a;  //b是左值引用
 int&& d = 10;  //正确,右值引用用右值初始化

 const int& f = a; //正确,左值常引用相当于是万能型,可以用左值或者右值初始化
 const int& g = 10;

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值