目录
链表
1. 一旦找到插入点或删除点,就可以插入或删除,而不需要在内存中移动数据项。
2. 在每一次插入和删除的过程,链表结构会调整大小,不需要额外的内存代价,也不需要复制数据项。
3.STL中提供的forward_list(单向链表),能够达到最好的手写链表的性能,推荐使用,forward_list中并没有提供X.size()操作。
4. list(双向链表)或forward(单向链表)两个容器的设计目的是令容器任何位置的添加和删除操作都很快速。作为代价,这两个容器都不支持元素的随机访问,为了访问一个元素,我们只能遍历整个容器。如果程序要求在容器的中间插入或删除元素,推荐使用list(双向链表)或forward(单向链表)。
指针和引用(左值引用)
1. 指针是一块内存的地址,引用是一个对象的别名(相当于小名)。
2. 引用必须是已有对象的引用,引用必须被初始化。
左值和右值
当一个对象被用作右值的时候,用的是对象的值(内容),当对象被用作左值的时候,用的是对象的身份(内存中的位置)。
右值引用只能绑定到一个将要销毁的对象。
堆和栈
1)内容
堆中主要储存new,malloc等手工分配的动态内存,必须手动释放。
栈中内容由系统自动分配,包括函数返回地址,参数,局部变量,返回值
2)效率
栈比堆的效率高。
原因:栈是操作系统提供的数据结构,具有计算机底层的支持,分配专门的寄存器存储栈的地址,压栈入栈有专门的指令执行,而堆是由c/c++函数库提供的,机制复杂,需要一些相关内存的算法,因此效率较低。
3)拓展方向
栈是由高地址向低地址进行拓展,堆是由低地址向高地址拓展
堆栈溢出(stack overflow)
向栈中某一变量写入的字节数超过了这个变量申请的字节数,导致相邻的变量的值被改变
原因:
1. 局部数组过大
2. 递归调用层次太多,递归函数在运行时执行压栈操作,当压栈次数太多时,也会导致堆栈溢出
3 .指针或数组越界。
new和malloc的区别
1.malloc本质上是函数,new 是c++的关键字
2.malloc需要给定申请内存的大小,返回的指针需要强转。new会调用构造函数,不用指定内存大小,返回的指针不需要强转。
面向过程与面向对象
面向过程是分析出解决问题所需要的步骤,然后用函数把这些步骤实现。
面向对象是将事物抽象为对象。
封装,继承,多态
封装:
将数据和操作数据的方法结合起来,形成类。外界不可以直接访问类中的数据,只能通过方法访问。
继承:
子类从父类中得到已有的特性,是is-a的关系。
多态:
根据指针或引用对象的不同调用不同的函数。
C与C++的区别
1)设计思想
c是面向过程,c++是面向对象
2)c++在c的基础上引入了面向对象,c++具有封装,继承,多态等特性。
Struct和class的访问权限
struct的数据访问控制是public,class的数据访问控制是private
static
1)变量
函数外定义的变量为全局变量,具有外部链接性(可在其他文件中访问),在程序整个运行过程中都存在。
外部变量前面加上 static后,具有内部链接性(只在本文件中访问)。
局部变量前面加上static后为局部静态变量,只在局部作用域有效,在程序整个运行过程中都存在。
未经初始化的全局静态变量会被自动初始化为0(自动变量如果为被初始化时未定义的)。
2)函数
函数默认具有外部链接性,加上static的话编程内部静态函数(必须在声明和定义中全部都加)。
3)类的静态数据成员
类的多个对象共享static类成员,对多个对象来说,静态数据成员只存储一处,供所有对象共用。
4)类的静态函数
类的静态函数和类的静态数据成员一样,都属于类的静态成员,他们都不是类的对象成员,因此对静态成员的引用不需要对象名。静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员。
静态成员函数使用格式:
类名::静态成员函数名()
C++中四种类型转换
Vector扩容问题
我们知道,vector 在需要的时候会扩容,在 VS 下是 1.5倍,在 GCC 下是 2 倍。那么会产生两个问题:
1)为什么是成倍增长,而不是每次增长一个固定大小的容量呢?
答:采用成倍方式扩容,可以保证常数时间的复杂度,而增加指定大小的容量扩容,只能达到O(n)的时间复杂度。
2)为什么是以 2 倍或者 1.5 倍增长,而不是以 3 倍或者 4 倍等增长呢?
答:采用2倍, 1,2,4,。。。每次重新分配都大于之前分配之和,不能重用,
采用1.5倍,1,2,3,4,6,可以重用之前的内存空间
递增递减运算符
递增递减运算符必须作用于左值运算对象。
1. 前置递增(减)运算符与后置递增(减)运算符的区别,如i++,++i.
前置运算符返回的递增后的运算对象,因为返回值是左值,因此可以继续再赋值。
int main() {
int i=0;
cout << ++i << endl;//输出1
++i = 5 ;//++i后为2,然后被赋值5
return 0;
}
后置运算符进行运算后,将初始值得副本作为返回值,返回值为右值。
int main() {
int j = 0;
cout << j++ << endl; //返回初始值得副本,此处为0
//j++ = 6; //由于返回值为右值所以不能被赋值
return 0;
}
2. 下列得程序会出错,出错原因:
赋值运算符的两端运算对象都用到it,并且右侧的运算对象还改变了it的值,所以该赋值语句是未定义的。编译器可能按照下的任意一种思路处理:
1)*it=toupper(*it)//如果先求左侧的值
2)*(it+1)=toupper(*it)//如果先求右侧的值
int main() {
string a{ "hello world" };
for (auto it = a.begin(); it != a.end() && !isspace(*it); ++it)
*it = toupper(*it++); //程序运行到此处会出错
cout << a;
}