此篇博客主要总结c++部分概念,包括不限于c++11,流,异常等
基础概念参考:基础概念笔记
面向对象参考:面向对象笔记
STL参考:
持续更新
目录
2.基类的虚函数表存放在内存的什么区,虚表指针vptr的初始化时间
14.auto、decltype和decltype(auto)的用法
1.结构体对齐
结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。
未特殊说明时,按结构体中size最大的成员对齐(若有double成员,按8字节对齐。)
2.基类的虚函数表存放在内存的什么区,虚表指针vptr的初始化时间
虚函数表是全局共享的元素,即全局仅有一个,在编译时就构造完成 ,虚函数表类似于类中静态成员变量,存在全局数据区。
虚表指针在实例化对象时进行初始化,并且存在对象内存布局的最前面。
3.宏定义和typedef区别
宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。
宏替换发生在编译阶段之前,属于文本插入替换;typedef是编译的一部分。
宏不检查类型;typedef会检查数据类型。
宏不是语句,不在在最后加分号;typedef是语句,要加分号标识结束。
注意对指针的操作,typedef char * p_char和#defifine p_char char *区别(*符号优先级)
4.strlen和sizeof区别
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.final和override关键字
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++;
(1)C++代码中调用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++中的重载、重写(覆盖)和隐藏的区别
重载是指在同一范围定义中的同名成员函数才存在重载关系。主要特点是函数名相同,参数类型和数目不同。
重写指的是在派生类中覆盖基类中的同名函数,重写就是重写函数体,要求基类函数必须是虚函数
重载与重写的区别:
- 重写是父类和子类之间的垂直关系,重载是不同函数之间的水平关系
- 重写要求参数列表相同,重载则要求参数列表不同,返回值不要求
- 重写关系中,调用方法根据对象类型决定,重载根据调用时实参表与形参表的对应关系来选择函数
隐藏指的是某些情况下,派生类中的函数屏蔽了基类中的同名函数:两个函数参数相同,但是基类函数不是虚函数;两个函数参数不同,无论基类函数是不是虚函数,都会被隐藏
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.auto、decltype和decltype(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 的作用是选择并返回操作数的数据类型。在此过程中,编译器只是分析表达式并得到它的类型,却不进行实际的计算表达式的值。
int e = 4;
const int* f = &e; // f是底层const
decltype(auto) j = f;//j的类型是const int* 并且指向的是e
15.大小端存储
大端存储:字数据的高字节存储在低地址中,存储结果同数字本身。
小端存储:字数据的低字节存储在低地址中
16.C++的异常处理的方法
C++中的异常处理机制主要使用try、throw和catch三个关键字
程序的执行流程是先执行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) 是一种内存分配方式。通常我们习惯直接使用new、malloc 等申请内存,容易造成内存碎片,内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块
allocator就是用来分配内存的,最重要的两个函数是allocate和deallocate,就是用来申请内存和回收内存的,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 指针
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
27.在类的析构函数中调用delete this
会导致堆栈溢出。原因很简单,delete的本质是“为将被释放的内存调用一个或多个析构函数,然后,释放内存”。显然,delete this会去调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃。
28.命令行参数
int main(int argc, char *argv[])
参数的含义是程序在命令行下运行的时候,需要输入argc 个参数,每个参数是以char 类型输入的,依次存在数组里面,数组是 argv[],所有的参数在指针char * 指向的内存中
29.为什么拷贝构造函数必须传引用不能传值?
30.静态函数能定义为虚函数吗
static成员不属于任何类对象或类实例,所以即使给此函数加上virutal也是没有任何意义的。
静态成员函数没有this指针。虚函数依靠vptr和vtable来处理。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;