C++知识点
C++
一、C和C++区别
-
- 关键字数量不同:c++有些关键字在C中没有,在C中可以作为函数和变量的标识
- 标准输入输出不同:C是printf和scanf,C++是cout和cin,cout和cin是类的实例,C++使用类来实现输入输出(C++仍旧可以使用printf和scanf)
- C++函数原型中可以声明一个或多个带有默认值的参数(从右向左连续)
- C++支持函数重载(底层命名规范不同)
- C和C++特性不同
- C++面向对象
- 封装、继承、多态三大特性
- OOP(Object-oriented paradigm)
- 封装、继承、多态三大特性
- C面向过程
- 性能高,调用类需要实例化,开销大;但是不易维护,不好复用
- C++面向对象
面向过程和面向对象的区别
-
- 面向过程性能比面向对象高,Linux/Unix一般使用面向过程;
面向对象因为需要调用类进行实例化,所以性能比面向对象低,开销大,比较消耗资源,但是面向对象耦合度低,更易维护,复用和扩展
-
- 面向对象的显著特征在于程序重点是数据而不是设计
程序被划分为所谓的对象
数据结构为表现对象的特征而设计
函数作为对某个对象数据的操作,与数据结构紧密结合在一起
数据被隐藏起来,不能被外部函数访问(封装)
对象之间可以通过 函数沟通
新的数据和函数可以在需要的时候较为简单的添加进来
在程序设计过程中遵循由下至上的设计方法
-
- 对象是在内存中的一块独立空间,里面包含着数据和操作行为,因为是独立的空间,对象可以不进行修改而运用到多个不同的程序中(复用)
- 实例化的时候是将内存中的对象进行“个性化”拷贝,面对不同程序,拷贝出来的对象是不同的使用和操作结果(多态)
-
- 面向过程的特点:
- 关注点在于算法
- 大程序被分割为小函数
- 大多函数共享数据
- 数据由一个函数流向另一个函数
- 采用从上到下的程序设计方法
- 面向过程的特点:
二、关键字
typedef:主要是定义别名
注意:typedef char* CHAR;
const CHAR s;实际是表示char* const,因为typedef是给char*这个整体取得别名,和宏不同点在于不是简单的字符串替换。
const:
在c++中,const是将对象转为常量。
-
- const指针可以接收const和非const地址,但是非const指针只能接收非const地址;多使用const指针
- 在函数中,一般设置const参数,并且是引用,不能把不是左值的地址传递给引用。(左值包括变量,数组元素,结构成员,引用,被解除引用的指针等)。 形参是const类型的,说明该函数将不会修改其值,该函数便为const函数。
- 保证不因函数内部修改一个const常量的值,就需要在函数最后加上const
void Fun(xxx)const;//声明
void Fun(xxx)const { }//定义
-
- #define没有类型检测机制,会有隐患,所以引入了const
sizeof:
-
- sizeof是操作符,作用是返回一个对象或类型所占的内存字节数,返回值是size_t
(size_t在头文件stddef.h中定义,它依赖于编译系统的值,一般定义为 typedef unsigned int size_t;)
-
- 语法形式:sizeof(对象)、sizeof 对象、sizeof(类型)
- sizeof不会对表达式进行计算
- 基本数据类型的sizeof和平台有关,不同平台不同值
- struct的sizeof与字节对齐有关
为什么要字节对齐?字节对齐有助于加快计算机取数速度,效率。
空结构体的sizeof为1,空结构体变量也被分配了空间来占位
-
- union的sizeof是每个成员的sizeof最大值
- 联合体是重叠式的
- 各成员共享同一段内存
- 数组的sizeof是素组所占的内存字节数
- 当字符数组表示字符串时,其sizeof将\0算了进去
- 当数组是形参的时候,是数组首地址的sizeof,也就是指针sizeof大小
- 指针的sizeof是内部地址总线的宽度,32位机器是4,与指针指向的对象没有关系
- 函数的sizeof是函数返回值类型的大小,函数不会调用
- 不能对返回值类型是空的函数求值
- 不可以对函数名求值
- 对有参数的函数,再用sizeof时候必须写上实参表(实参表就是多个实参)
- union的sizeof是每个成员的sizeof最大值
inline:在函数声明或定义中函数返回类型前加上关键字inline即把min()指定为内联
inline int min(int first,int second){/****/};
-
- inline对编译器而言必须是可见的,以便在调用的时候展开函数。如果要调用inline函数,必须在所在的文件中定义。(建议放在头文件中)
- 关键字inline必须与函数定义体放在一起才能成为内联,仅将inline函数放在函数声明前是不起任何作用的
- 定义在类中的成员函数自动成为内联函数
- 慎用内联:内联 能提高函数的执行效率,但是是以代码复制也就是代码膨胀作为代价换取执行效率的,仅仅省去了函数调用的开销,在调用内联函数的时候会复制代码,将整个程序的代码量增多,消耗内存空间
- 不适合用inline的情况:
- 函数代码较长,inline会导致内存消耗增加
- 函数内部有循环,不适合用内联,因为执行循环的开销要比调用函数的开销大
static:c++常用修饰符,用来控制变量的存储方式和可见性
-
- 为什么引入static???
需要调用同一个函数多次,但是不想让某一个变量每次被创建每次函数结束时候又被释放掉,所以使用静态局部变量,它的生命周期是本文件的结束时候才被释放掉,保证这个变量只受某个函数控制。
-
- 什么时候用static??
一个数据对象要对整个类而非某个对象服务,同时力求不破坏类 的封装性,这个成员的外部不可见性(失去外部链接属性)
-
- static内部机构??
静态数据成员要在程序一开始运行就必须存在,分配空间要在类的外部接口的头文件、类内部的实现或者main函数前面的全局定义处
静态成员要实际的分配空间,故不能在类的声明中定义(只能声明数据成员),不能在类中写定义,只能声明类成员函数
-
- static优势:
- 节省内存
- 提高时间效率
- static优势:
注:类的静态成员函数属于类而不是对象,没有this指针,只能访问类静态数据和静态成员函数
extern:
-
- 标识此函数或变量在别的模块中,使用需要在别的地方寻找(置于变量或函数前)
- 链接指定
extern "C" void Fun( int a,int b );//告诉编译器这个函数按照C的编译规则去解析函数名
在头文件中,extern int g_Int;则修饰g_Int的作用域,可以包含在此文件或者其他文件中使用
在 *.c中定义了一个变量,在*.h中使用extern来声明,需要用到此变量只需要添加.h文件就可以了,或者在本文件前使用extern来声明一次变量
-
- 使用extern需要保持类型的统一
- 使用extern "C" 来声明函数使用C语言规则解析函数名,因为C++有函数重载的原因,函数名的解析是函数名+参数生成的中间名称,为了不使用这种规则,就使用extern声明
new/delete、new/delete[ ]:C++动态申请和释放空间的关键字,不是函数
-
- malloc不会对空间进行必要的初始化,但是new可以初始化
- operator new和operator delete
char* pa=new A(10);//A是类,构造函数打开一个文件,析构函数关闭文件
-
- operator new 申请一块未初始化的空间,调用类的构造函数初始化空间,pa指针指向这块已经初始化的空间
- operator delete请求调用析构函数解除这块空间与文件之间的指针,delete释放空间内存
- 申请、释放一个数组
- new/delete[ ]
string *psa = new string[10]; //string是自定义类型所以在申请空间时候会根据string类 的构造函数初始化每个元素
int *pia = new int[10]; //基本类型开辟空间后不会初始化
delete释放空间
delete [] psa;//调用析构函数再释放对象空间,在分配空间时候多分配四个字节保存维度以便于释放空间的时候知道应该调用几次析构函数 (指向空间的指针直接指向的是i后面的空间),最后释放空间的时候也要释放表示维度的4个字节
i(表示元素个数) |
x |
x |
x |
x |
delete [] pia;//因为是内置类型,所以直接释放内存空间
-
- new和delete、new[]和delete[]要配套使用,防止内存泄漏和程序崩溃等严重问题
auto(c++11):1.自动类型推断
auto v=t*u;//利用初始化表达式中推断数据类型
2.返回值占位
template <typename T1, typename T2>
auto compose(T1 t1, T2 t2) -> decltype(t1 + t2)
{
return t1+t2;
}
auto v = compose(2, 3.14);
-
- auto声明的变量必须初始化
- 可以使用const 、* 、** 、来修饰auto
- 不能和其他类型组合使用
- 函数和模板参数不能使用auto
- 不能进行类型转换
- auto必须始终推倒为同一类型
for(auto n:s){}内部借助迭代器
三、类和对象
-
- 基本概念
- 访问限定符
- public:public成员可在类外直接访问
- protected:类外不可直接访问,派生类可以访问(在继承部分才有用)
- private:在类外不可直接访问
- const修饰成员函数
- 用法:在成员函数后面加const,void Fun( )const,编译器可处理为void Fun(const T* this)
- 作用:const修饰this指针多指向的对象,保证了这个const成员函数的对象在函数内部不会改变
- inline内联函数
- 特点
- inline对于编译器只是一个建议,编译器会自动设置是否设置当前函数为内联函数
- 在编译期间编译器会将内联函数调用地方展开,没有函数压栈开销
- 定义在类内部的成员函数默认定义为内联函数
- 在debug版本中没有真正的内联,可以进行调试;release版本下才会真正实现内联
- 不适合内联的情况
- 函数体代码过长,使用内联会导致代码膨胀臃肿
- 数据开销大比如函数体内有循环,执行函数体的开销比调用函数的开销大,不适合用内联
- 特点
- 友元函数
- 用法,在函数前加friend
- 注意
- 友元函数不是类的成员函数
- 友元函数允许在类外访问该类中的任何成员
- 友元函数一定程度上破坏了封装性
- 类的静态成员
- 类里面static修饰的成员称为静态成员
- 类的静态成员是该类的所有对象所共享
- 静态成员变量在类外定义并初始化
- 静态成员函数没有隐含this指针参数,使用::调用静态成员函数
- 可以直接通过类名加::来调用静态成员函数,非静态的不可以
- 类的对象可调用静态成员函数和非静态成员函数
- 在类的静态成员函数内部不可以使用类的非静态成员;反之,在类的非静态成员函数内部可以使用静态成员,可编译通过(静态调静态,非静态调静态)
- 访问限定符
- 基本概念
非静态成员需要类的实例化初始化后才能使用,如果允许静态成员函数调用非静态成员,或许非静态成员还未初始化,所以是错误 的
-
- 类和对象的实例化
- 静态建立:A a(直接调用构造函数)
- 是由编译器直接在栈空间中分配内存,移动栈顶指针挪出适当的空间,在这片内存空间中调用构造函数形成栈对象
- 静态建立:A a(直接调用构造函数)
- 类和对象的实例化
-
- 动态建立 A* ptr=new A(operator new+构造函数)
- 第一步是operator new()函数开辟空间,第二步是调用构造函数初始化空间
- 面试题
- 如何创建一个类只能在堆上
- 将析构函数私有化就不会建立在栈上了
- 如何创建一个类只能在栈上
- new的时候会在堆上创建对象,将operator new()私有化就只能在栈上建立
- 如何创建一个类只能在堆上
- 6个默认成员函数
- 构造函数
- 特征
- 函数名同类名,无返回值,可重载
- 对象构造或实例化是系统自动调用构造函数
- 缺省的构造函数只能有一个
- 初始化方式
- 初始化列表
- 必须在初始化列表初始化的成员
- const常量成员变量(const对象只能初始化不能赋值)
- 引用类型的在创建之初必须初始化
- 没有缺省构造的类成员变量
- 初始化列表初始顺序(按照声明顺序初始化)
- 变量先定义的先初始化,后定义的后初始化
- 必须在初始化列表初始化的成员
- 初始化列表
- 特征
- 构造函数
- 动态建立 A* ptr=new A(operator new+构造函数)
Test(int x)
:b(x)
,a(b)
{}
int a;
int b;
先初始化a,此时b为随机值,所以a也为随机值;
再初始化b,b初始化为1
-
- 构造函数内部初始化
- 一般的赋值初始化
- 构造函数内部初始化
-
- 拷贝构造函数
特征
-
- 必须使用引用传参,防止在传参过程中又拷贝一次,再次调用拷贝构造函数,无线循环,递归崩溃
- 无返回值,函数名同类名
- String(const String& s)
- 析构函数
特征
-
- 生命周期结束自动调用
- 赋值运算符重载
- 赋值运算符和拷贝构造的区别
- 赋值运算符重载是对已有的对象进行拷贝赋值
- 拷贝构造是用已有的对象初始化新创建的对象
- 返回值
- Date& operator=(),返回值是Date&,是为了支持链式赋值
- 赋值运算符和拷贝构造的区别
- 取地址操作符重载
类名+operator&()
-
- c++类操作符重载可以重载成类成员函数
- 也可以重载成友元函数
- const修饰的取地址操作符重载
friend std::ostream & operator<<(std::ostream & os,const Time & t)
{.....}
-
- class和struct的区别
- 默认访问限定符不同:class是private,struct是public
- 默认继承方式不同:class为private,struct是public继承
- class能够定义模板参数,struct不行
- class和struct的区别
四、内存管理
-
- 动态内存管理
- new的用法
- new T;
- new T(x);构造一个T类型对象并将值初始化为x
- new T[n];构造n个T类型的对象
- new的定位表达式:new(place_address)type(initializers)
- 创建对象但是不分配内存,而是在已有的内存上创建对象,用于需要反复创建删除的对象上,可以降低内存分配的性能消耗
- new的用法
- 动态内存管理
place_address是表达式,同时提供一个(可为空)以逗号分隔的初始化列表
-
- 使用new的定位表达式后,要删除空间,需要先调用对象的析构函数,只有当整片空间不用后,再显式调用delete释放提前开辟的空间
-
- malloc/free和new/delete的区别和联系
- 都负责动态管理内存
- malloc/free是C/C++标准库的函数,而new/delete是C++操作符
- malloc/free是负责动态分配/释放空间;而new/delete除了分配空间还会调用构造函数和析构函数进行初始化
- malloc需要手动计算类型大小且返回值为void*,new可自己计算并返回对应类型的指针
- new/delete调用过程
- new/delete
- new
- 调用operator new分配空间(malloc的一层封装)
- 调用构造函数初始化对象
- delete
- 调用析构函数清理对象
- 调用operator delete释放空间(free的一层封装)
- new
- new[]/delete[]
- new[]
- 调用operator new[ ]开辟所需空间再加4个字节的存储元素个数
- 使头指针从空间初始位置向后移动4个字节,以为前面的4个字节存储的是元素个数,调用N次构造初始化
- delete[]
- 先去头指针再向前4个字节开始取到N,进行N次析构
- 从头指针再向前4个字节开始,一次性调用operator delete释放空间
- 只有当自定义类型且显示自定义析构函数时编译器才会多开四字节存放元素个数
- new[]
- new/delete
- 一定要注意malloc/free,new/delete,new[]/delete[]的匹配使用
- 字节对齐(内存对齐)
- malloc/free和new/delete的区别和联系
为什么要字节对齐?
提高效率,减少读取内存的次数
结构体的对齐同C语言
-
- 内存泄漏
- 指的是程序未能完全释放掉不再使用的内存,使得应用程序失去了对这些内存的控制,造成内存浪费
- 后果:大量内存泄漏或日益增多,从性能上就会降低直到内存完全耗尽;也使得别的程序无法查找问题的真正根源
- 系统资源泄漏
- 堆内存泄漏
- 解决内存泄漏最有效的办法就是智能指针
- 内存泄漏
智能指针能自动删除分配的内存,不需要手动释放指针,而是通过智能指针自己管理内存的释放
-
- 深浅拷贝
- 深拷贝:每次都直接开辟空间,每个对象指向不同的地址
- 引用计数的写时拷贝(浅拷贝):先都指向同一片空间,并且配上一个引用计数;等待需要进行修改操作时再拷贝空间出来
- 注意:对于指针地址拷贝,默认为浅拷贝,所以会出现析构多次的情况导致崩溃
- 面试中string类的正确写法
- 深浅拷贝
-
-
继承和多态
- 继承概念
- 继承三种关系
- public
- 继承完成后,基类的非私有成员在子类的访问属性不变
- protected
- 基类的非私有成员都成为子类的保护成员
- private
- 基类的非私有成员都变成子类的私有成员
- public
- 继承与转换
- 子类对象可以赋值给父类对象(切片);父类对象不可以赋值给子类对象
- 父类的指针或者引用可以指向子类对象;子类的指针或引用不能指向父类对象(可通过强制转换实现)
- 继承三种关系
- 继承概念
-
继承和多态
访问控制和继承
派生类可以访问基类中所有非私有成员,因此基类成员如果不想被派生类的成员函数访问就可以在基类中声明为private
一个派生类继承了所有的基类方法,但排除下列情况:
-
- 基类的构造函数、析构函数和拷贝构造函数
- 基类的重载运算符
- 基类的友元函数
-
- 公有继承:基类的公有成员和保护成员被派生类继承为公有成员,但是不能继承基类的私有成员,派生类只能通过继承下来的公有和保护成员来间接访问基类的私有成员;
- 保护继承:基类的公有成员和保护成员被派生类继承为保护成员
- 私有继承:基类的公有和保护成员被派生类继承 为私有成员
-
- 多继承
- 多继承就是一个子类可以有多个父类,继承了多个父类的特性
- class A:class B,class C.......{};
- 环状继承A->D,B->D,C->(A,B),这个继承会使得D创建两个对象,要解决上面问题就要有虚拟继承格式
- 多继承
A:virtual public D
B:virtual public D
-
- 派生类的默认成员函数的合成
- 在派生类中如果没有显示的定义六个成员函数,编译系统就会默认合成这六个默认成员函数
- 在初始化的时候,创建派生类对象时程序调用派生类构造函数,然后编译器在初始化列表中调用基类的构造函数初始化基类的数据,派生类构造函数主要初始化派生类新增的数据成员;析构的时候先调用派生类的析构函数,在函数体执行完再调用基类的析构函数析构(继承的数据成员生命周期长,派生类的数据成员生命周期短)
- 拷贝构造函数:将新的对象初始化为一个同类对象、按值将对象传递给函数、函数按值返回对象、编译器生成临时对象
- 程序没有显示定义拷贝构造函数,编译器将自动生成一个,当然,如果想在派生类中构造基类对象,那么不仅仅可以用构造函数,也可以用拷贝构造函数
- 赋值操作符也是没有显示定义就会使用默认的
- 继承与静态成员
- 如果基类中有static静态成员,那么不论这个基类有多少派生类,都是和基类共享此pubic或protected的静态成员,但是private就不能被派生类访问到
- 为什么要有继承??
- 继承可以代码复用,可以将多个类公有的成员抽象出来作为父类,其他的类继承该类,并分别再实现各自持有的特性
- 派生类的默认成员函数的合成
-
- 菱形继承相关问题
- 二义性:一般来说,在派生类对基类的成员访问应该是唯一的,由于多继承情况下,可能造成对基类中某成员的访问出现了不唯一的情况,则成为对基类成员访问的二义性问题
- 如果派生类C继承了基类A和B,并且基类A和B中都有Print函数,那么在派生类调用基类print函数的时候,会不知道调用哪个print函数,于是出现了二义性;解决的办法是在访问时候进行作用域的限定,使用A::Print()或者B::Print()来限定访问的是哪个基类的函数;如果不加以限定,则会出现二义性问题:
- 除了使用作用域限定的方法,最好的方法是在派生类C中再次定义同名成员函数,类C中的同名函数根据情况决定调用A还是B中的同名函数;
- 在派生类C中定义的同名函数也会出现二义性问题,可以在C中的同名函数前加上作用域限定符限定调用哪个基类的函数
- 若派生类C中有和其中一个基类同名的函数,不会产生二义性,因为派生类支配基类,所以C中的同名函数支配基类的同名函数,可以选择支配者的那个函数
- 由于二义性,一个类不能被同一个类继承一次以上
- 菱形继承
- 虚继承:为了解决从不同路径继承来的同名数据成员在内存中有着不同的拷贝构造数据不一致的问题,将共同基类设置成虚基类,解决了二义性的问题,避免数据不一致
- 菱形继承就是
- 如果派生类C继承了基类A和B,并且基类A和B中都有Print函数,那么在派生类调用基类print函数的时候,会不知道调用哪个print函数,于是出现了二义性;解决的办法是在访问时候进行作用域的限定,使用A::Print()或者B::Print()来限定访问的是哪个基类的函数;如果不加以限定,则会出现二义性问题:
- 二义性:一般来说,在派生类对基类的成员访问应该是唯一的,由于多继承情况下,可能造成对基类中某成员的访问出现了不唯一的情况,则成为对基类成员访问的二义性问题
- 菱形继承相关问题
class A{};
class B:public A{};
class C:public A{};
class D:public B,public C{};
-
- 如果不用虚继承就会导致模糊调用的现象
class B:virtual public A{};
class C:virtual public A{};
-
- 解决方法:虚继承(虚基表的推出,虚基表中存放了现对于当前位置虚表地址以及父类地址的偏移量)
- 缺点:数据冗余和二义性
- 多态和虚函数
- 基础概念
- 作用:一个接口,多种实现方法
- 多态条件:
- 父类的指针或引用
- 虚函数重写(协变)
- 注意:若父类为虚函数,子类virtual可写可不写;若父类不是虚函数,子类即使是虚函数也不构成多态
- virtual关键字
- 函数完全相同(函数名、参数列表、返回值),或者满足协变(协变是指返回值必须是父子关系的指针或引用)
- 构成多态和类型无关,与对象有关,否则和参数对象有关
- 多态分类:静态联编/动态联编
- 动态联编:满足多态,看对象,到对象的虚表中找虚函数地址(运行时决定)
- 静态联编:普通调用,看类型(编译时决定)
- 多态的对象模型
- 虚函数表(虚表)
- 虚函数存在代码段中
- 单继承时,子类直接使用父类的虚函数表;若构成重写则直接覆盖对应位置上的函数
- 多继承时,子类优先使用先继承的父类的虚函数表,将自己的虚函数放在第一个继承的父类的虚函数表中
- C++虚函数表解析
- 在单继承和多继承中虚函数表的存储
- C++对象内存布局
- 虚函数表(虚表)
- 多态的实质
- 当父类指针/引用 指向父类对象的时候,到父类的虚函数表中取得相对应的虚函数;指向指针/引用子类对象时,则去子类的虚函数中找
- 虚函数
- 纯虚函数
- 用法:在成员函数的形参后学号是哪个=0,说明该成员函数 为纯虚函数
- 包含纯虚函数的类叫抽象类(接口类),抽象类不能实例化出对象
- 纯虚函数在派生类中重新定义之后,派生类才能实例化出对象,也就是强制重写该虚函数,否则实例化不出对象
- 注意
- 只有类的成员函数才能定义为虚函数
- 静态成员函数不能定位为虚函数
- 构造函数不能为虚函数,最好不要将operator=定义为虚函数
- 不要在构造函数和析构函数中调用虚函数,可能会发生未定义行为
- 最好把基类的析构函数声明为虚函数(析构函数虽然名称不一样,但是也可以覆盖,编译器底层进行处理)
- 纯虚函数
- 基础概念
- 重点
- 为什么要将基类的析构函数定义为虚函数??
- 按照对象调对应的析构函数
- 重载、重写、重定义
- 重载:一个类中的水平的同名不同参,或者不同返回值
- 重写(覆盖):不同类(基类和派生类)中垂直的同名、同参、同返回值的函数重写
- 重定义(隐藏):在不同类(基类和派生类)中函数名相同(在不同类中不构成重写就是重定义)
- 友元函数不能继承,基类友元不能访问子类私有和保护成员
- 为什么要将基类的析构函数定义为虚函数??
-
- 模板
- 模板分类
- 模板函数
- template<class T>
- 模板函数
- 模板分类
- 模板
void fun(const T& x){ }
-
- 模板类
- template<class T>
- 模板类
class 类名{ };
-
- 模板的原理
- 编译器的推演,实例化后生成对应代码
- 模板参数
- 模板参数
- template <class T,Container=Seqlist<int> >
- 模板参数
- 模板的原理
class Stack{
private:
Container _con;
};
-
- 模板的模板参数
- template <class T,template<class>class Container=Seqlist >
- 模板的模板参数
class Stack{
private:
Container<T> _con;
};
-
- 非类型的类模板参数
- template <class T,size_t MaxSize=10>
- 非类型的类模板参数
class A{
private:
T arr[MaxSize];
};
-
- 非类型的模板函数参数(浮点数和类对象不允许做 非类型的模板参数)
- template <class T,int value>
- 非类型的模板函数参数(浮点数和类对象不允许做 非类型的模板参数)
T Add(const T& ss){
return value+ss;
}
-
- 模板的特化
- 全特化
- 偏特化
- 模板的分离编译
- 为什么不能分离编译?
- 模板的特化
分离编译,模板 没有被实例化,只能看到.h文件的函数声明,找不到函数的具体实现
-
- 智能指针
- RAII思想
- 资源分配即初始化,使用类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数中完成资源的清理,保证资源的正确初始化
- 出现原因
- 有时候会遇到执行流的跳转导致指针未能按照正常逻辑走
- 四种智能指针
- auto_ptr(C++98)
- 自动指针,最后一个指向该空间的指针拥有权限
- scoped_ptr(boost库)
- 为了解决自动指针中访问旧指针出现错误
- 防拷贝,将拷贝函数和赋值运算符重载设为private,只声明不定义
- shared_ptr(boost库\C++11)
- 引入引用计数来控制拷贝和析构(两个指针指向同一片空间)
- weak_ptr(boost库\C++11)
- 专门用来解决shared_ptr中的循环引用缺陷问题
- 什么是循环引用缺陷问题??
- 两个节点相互之间有指针限制,将_prev和_next指针都改为weak_ptr,就不会出现循环引用问题了,因为weak_ptr不会增加shared_ptr所指向空间的引用计数,释放的时候不会出现循环引用的问题
- unique_ptr(c++11)
- 如同scoped_ptr防拷贝
- auto_ptr(C++98)
- RAII思想
- 智能指针
使用智能指针尽量不使用free/malloc
-
- 发展历史
- 强制类型转换
- static_cast
- 非多态类型的转换(静态类型转换)
- 不能用于两个不相干的类型进行转换
- reinterpret_cast
- 转换成不同类型
- const_cast
- 删除变量的const属性,方便赋值
- dynamic_cast
- 将一个父类对象的指针转换为子类对象的指针或引用(动态转换)
- 注意:子类对象指针--->父类指针/引用(不需要转换):父类对象指针--->子类指针/引用(用dynamic转换是安全的)
- dynamic_cast只能用于会先检查是否能转换成功,能成功则转换,不能则返回0
- explicit关键字
- explicit关键字是阻止经过转换构造函数进行的隐式转化的发生
- 隐式转换:是将实参调用构造函数定义了从形参到该类型的转换
- explicit关键字是阻止经过转换构造函数进行的隐式转化的发生
- static_cast
- 发展历史
-
- 异常
- 异常处理的概念
- 当函数遇到自己无法处理的错误时候抛出异常,让函数调用者直接或间接的处理问题
- 异常处理流程
- 抛出异常的时候,将暂停当前函数的执行,开始查找对应的匹配Catch子句;首先检查throw本身是否在catch块内部,如果是再查找匹配的catch语句,如果有匹配的就处理,没有就退出当前函数栈,继续再调用函数的栈中进行查找;直到到达main函数的栈,依旧没有匹配的处理则终止程序;找到匹配的catch子句并处理后,会沿着catch子句后面继续执行
- 使用手册
- 异常处理的概念
- 异常
-
- 引用
- 引用的特点
- 一个变量可以取多个引用别名
- 引用必须初始化
- 引用一旦指定,不能再引用其他变量
- 引用是对应变量内存地址的别名
- 指针和引用的区别
- 引用只能在定义时候初始化一次,且之后不能改变指向;指针可以改变
- 引用必须指向有效的变量,指针可以指向NULL
- 引用和指针的++、--意义不同
- sizeof指针对象和引用对象的意义不一样,sizeof引用是所指向变量的大小,sizeof指针是指针本身的大小,占4个字节(32位)
- 指针访问指向对象时需要解引用,而引用不需要
- 注意
- 不要返回一个临时变量的引用
- 如果返回对象除了当前函数的作用域依旧存在,最好引用返回,因为高效
- 传值返回时,是将ret的值放到eax寄存器中
- 传引用返回时,是取ret的地址放到eax寄存器中
- 数组的引用:int arr[3]=arr; int (&a)[3]=arr;括号才说明是引用,不加括号会报错,因为可以创建对数组的引用,但不能创建一个每个元素都是引用的数组
- 引用不一定是消耗存储空间,c++没有规定引用的底层实现,看编译器实现
- 引用的特点
- 引用
-
- 五个不能重载的操作符
- sizeof
- 三目运算符 ? :
- 与运算符
- 成员访问限定符
- 成员指针访问运算符 .*
- 库函数和系统调用的区别
- 在所有的ANSIC编译器版本中,C库函数是相同的;而各个操作系统的系统调用是不同的
- 库函数是调用函数库中的一个程序,而系统调用时调用系统内核的服务
- 库函数是在用户地址空间执行,而系统调用时再内核地址空间执行
- 库函数属于过程调用,开销较小;系统调用需要在用户态和内核态切换,开销较大
- 五个不能重载的操作符
动态绑定?
在给定过程调用相关联的代码只有在运行期才知道,与多态和继承的关系紧密。
消息传递?
一个面对对象有许多对象组成,需要对象进行沟通。
主要的程序设计步骤是:
1.创建类,类定义了对象及其操作行为
2.由类定义创建对象
3.建立对象之间的通信
对象的消息就是调用操作函数的过程
注:对象有生命周期,只有在生命周期内才可以相互通信
OOP(面向对象编程)优点:
1.继承可以减少代码量,扩展现有代码
2.标准模块间 建立联系的协议构建程序,也就是在*基础上写代码,提高效率
3.数据隐藏可以避免外部数据的侵蚀
4.允许 一个对象有多个实例存在并不会相互干扰
5.允许将问题空间的对象直接映射到程序中??
6.基于对象的工程很容易分割为独立的部分
7.以数据为中心的设计方式可以帮助抓取实现模型的更多细节
8.面向对象很容易将程序有小到大进行扩展
9.面向对象传递消息的方式比外部接口传递更简单
10.更便于控制软件复杂度
c++函数重载:
C++允许函数名相同的进行函数重载,即函数名相同但是参数名不同,因为c++会在解析函数名的时候结合参数生成中间函数名,同名的函数也就生成了不同的函数
为什么不将函数返回类型考虑到函数重载中呢?
——这是为了保持解析操作符或函数调用时,独立于上下文(不依赖于上下文)
c++重载运算符:
自定义类型的运算符重载
9 // 操作符重载方式一
10 Output& operator>>(string b)
11 {
12 cout << b;
13 return *this;
14 }
15
16 // 操作符重载方式二
17 friend Output& operator>>(Output& out, int i)
18 {
19 cout << "output a integer: " << i;
20 return out;
21 }
22
23 Output operator!(void)
24 {
25 cout << "Hehe!\n";
26 return *this;
27 }
来自 <http://www.cnblogs.com/zhanghang-BadCoder/p/7596486.html>
class String {
public:
String()
:_data(new char[1]){
_data = '\0';
}
~String() {
delete[]_data;
}
String(const char* str)
:_data(new char[strlen(str)+1]){
strcpy(_data, str);
}
String(const String& rhs)
:_data(new char[rhs.size()+1]){
//c_str()是string类转为char*的字符串,C语言没有string,所以需要c_str()兼容
strcpy(_data, rhs.c_str());
}
//传统写法
String& operator=(const String& rhs) {
String tmp(rhs);
swap(tmp);
return *this;
}
String& operator=(String rhs) {
swap(rhs);
return *this;
}
String(String&& rhs)
:_data(rhs._data) {
rhs._data = NULL;
}
String& operator=(String&& rhs) {
swap(rhs);
return *this;
}
size_t size() const {
return strlen(_data);
}
const char* c_str() const{
return _data;
}
void swap(String& rhs) {
std::swap(_data, rhs._data);
}
private:
char* _data;
};