C++基础知识复习
第一部分:基础知识
一、const
1. 作用
修饰变量,表示不可能更改
修饰指针
const int *ptr
——pointer to constint const *ptr
—— const pointer- 原则:被const修饰的后面的值是不可改变的
修饰引用
常用于形参。即避免了copy,又避免了对其值的修改
修饰成员函数
表示该成员函数不能修改成员变量
二、static
1. 作用
修饰普通变量
修改变量的存储区域,使其存储在
静态区。
在main函数执行前就分配了空间;
如果没有被显式初始化,则用默认值初始化
修饰普通函数
表明函数的作用范围。
仅在定义该函数的文件内才能使用(多人项目中,为了防止与其他namespace重名,可以将函数定义为static)
修饰成员变量
使其变为类变量,即所有instance公用一个变量
修饰成员函数
使得不需要构造对象就可以访问此函数
static函数内不能访问非静态成员(毕竟人家不要构造实例)
三、this指针
this
指针是一个隐含于每一个非静态成员函数中的特殊指针,指向调用该成员函数的那个对象。(类似ptyhon类中的self
)- 当对一个对象调用成员函数时,程序先讲对象的地址赋给
this
指针,然后调用成员函数;每次成员函数存取数据变量时,都隐式地使用this
指针 this
指针被隐式地声明为ClassName *const this
——意味着不能给this
指针赋值;在ClassName
类的const
成员函数中,this
指针的类型为:const ClassName* const
,这说明不能修改this
指针指向的数据成员。this
并不是一个常规变量,而是一个右值,所以不能取得this
的地址(能不能&this
)- 以下场景中,经常显式的引用
this
指针:- 为实现对象的链式引用
- 为避免对同一对象进行赋值操作
- 为实现一些数据结构时,如
list
四、inline内连函数
1. 特点
- 编译时,将内联函数内容在调用处展开
- 省略了进入函数的步骤,直接执行函数体
- 类似于
宏
,但多了类型检查,具有函数特性 - 编译器一般不inline包含循环、递归、switch等复杂操作的函数
- 类中定义的函数,除了虚函数之外,其他函数都会自动隐式地的当成内联函数。
2. 编译器对inline函数的处理步骤
- 将
inline
函数体复制到inline函数调用处 - 为所有
inline
函数中的局部变量分配内存空间 - 将
inline
函数的输入参数和返回值映射到调用方法的局部变量中 - 如果
inline
函数包括多个返回点,将其转变为inline
函数代码块末尾的分支(使用GOTO)
3. 优点
- 省去了参数压栈、栈帧开辟与回收,结果返回的开销
- 相比与
宏
来讲,在代码展开时,会做安全检查
或者自动类型转换
- 在类中声明同时定义的成员函数,会自动
inline
,因此内联函数可以访问类的成员变量,但宏
不可以 - 内联函数在运行时可调试,宏定义不可以
4. 缺点
- 代码膨胀。若执行函数体的时间比函数调用大很多,则
inline
收益很小。 inline
无法随着函数库升级而升级。inline
函数的改变需要重新编译,而non-inline
可以直接连接- 是否内联,程序员不可控。
inline
函数只是对编译器的建议,是否内联,由编译器决定。
5. 虚函数可以是inline
吗
- 可以。但当虚函数表现多态时的时候,不能内联
- 内联发生在编译时,而虚函数的多态性是在运行期,编译器无法知道运行时调用哪个函数。因此虚函数表现为多态时(运行期)不能内联
inline virtual
唯一可以内联:编译器知道要调用的对象是哪个类(如Base::who()
),这只发生在编译器具有实际对象,而非对象的指针或引用。
例子:
#include <iostream>
using namespace std;
class Base {
public:
inline virtual void who(){cout << "Base::who"<<endl;}
virtual ~Base()}{}
}
class Derived : public Base {
public:
void who(){cout << "Derived::who"<< endl;}
}
int main(){
Base b;
b.who(); // 此处编译其就知道要调用哪个函数
Base *ptr = new Derived();
ptr->who();// 多态
delete ptr;
ptr = nullptr;
return 0;
}
五、sizeof
- 对数组,返回整个数组占用的空间大小
- 对指针,返回指针本身占用的空间大小
六、extern "C"
- 被
extern
的函数或变量是extern类型,跨文件访问 - 被修饰的变量或函数是按照C语言方式编译和链接的
#ifdef __cplusplus
extern "C"{
#endif
void *memset(void *, int, size_t);
#ifdef __cplusplus
}
#endif
七、union联合
1. 概念
可以有多个数据成员,但在任意时刻只有一个数据成员可以有值。当某个成员被赋值后,其他成员变成未定义状态。
2. 特点
- 默认为public
- 可以有构造函数、析构函数
- 不能含有引用类型成员
- 不能继承自其他类,不能作为基类
- 不能包含虚函数
- 匿名union在定义所在的作用域可以直接访问union成员
- 匿名union不能包含
protected
和privated
成员 - 全局匿名union必须是静态的
#include <iostream>
using namespace std;
union UnionTest {
UnionTest():i(10){}; // 构造函数
int i;
double d;
};
static union{ // 全局静态匿名union
int i;
double d;
};
int main(){
UnionTest u;
cout << u.i << u.d << endl;
Union { // 局部匿名union
int i;
double d;
};
::i = 20; // 全局静态union.i值
i = 30;// 局部i
return 0;
}
八、C实现C++类
封装
使用函数指针把属性和方法封装在结构体中
继承
结构体嵌套
多态
父类与子类方法的函数指针不同
九、explicit
- 修饰构造函数是,防止隐式转换和复制初始化
- 修饰转换函数时,防止隐式转换,但按语境转换除外
十、friend友元类和友元函数
- 能访问私有成员
- 破坏了封装性
- 友元函数不可传递
- 友元函数的单向性
- 友元声明的形式和数量不受限制
十一、using
1. 引入命名空间的一个成员
using namespace_name::name;
2. C++11中派生类重用基类的构造函数
class Derived : public Base{
public:
using Base::Base;
};
对于基类的每个构造函数,编译器都会生成一个与之对应(形参类列表完全相同)的派生类构造函数。
3. using指示
using namespace std;// 无需为std里的所有名字添加std前缀了
注:
- 应尽量少用
using 指示
,会污染命名空间- 如果只引入一个成员,且与局部名称冲突了,编译器会发出指示
- 但如果全导入了,且覆盖了局部名称,编译器不会提示,排查问题较难
十二、::范围解析符
1. 类别
::name
全局作用符。用于类型名称(如类、类成员、成员函数、变量)前。
class::name
类作用域符。用于指定类型的作用域范围是具体某个类的。
namespace::name
命名空间作用域符用于指定类型的作用域范围是某个命名空间的。
int count = 1; // ::count
class A{
public:
static int count = 2; // A::count
};
void foo(){
int count = 3;
}
十三、引用
1. 左值引用
常规引用,表示对象的身份。
2. 右值引用
右值引用就是必须绑定到右值(一个临时对象、将要销毁的对象)的引用,一般表示对象的值。
右值引用可以实现转移语义(move sementics)和精确传递(perfect forwarding),主要目的有两个:
- 消除两个对象交互时不必要的对象拷贝,节省存储资源,提升效率
- 能够更简洁明确地定义泛型函数
3. 引用折叠
X& &
、X& &&
、X&& &
可以折叠为X&
X&& &&
可以折叠为X&&
十四、成员初始化列表
1. 优点
更高效
少了一次调用默认构造函数的过程
有些场合必须要初始化列表
常量成员
因为常量只能初始化,不能赋值,所以必须放在初始化列表中
引用类型
引用必须在定义时初始化,并不能重新赋值
没有默认构造函数的类类型
因为使用初始化列表可以不必调用默认构造函数,就可初始化
2. initializer_list
用花括号初始化器初始化一个列表,其中构造函数接受一个std::initializer_list
参数
#include <iostream>
#include <vector>
#include <intializer_list>
template<typename T>
struct S{
std::vector<T> v;
S(std::initializer_list<T> l): v(l){
std::cout << "init"<< endl;
}
void append(std::initializer_list<T> l){
v.insert(v.end(), l.begin(), l.end());
}
std::pair<const T*, std::size_t> c_arr() const {
return {&v[0], v.size()};
}
};
第二部分:面向对象
一、多态
1. 概念
多态,可以理解为消息以多种形式显示的能力
多态是以封装和继承为基础的
C++多态分类和实现
重载多态(Ad-hoc,编译期)
函数重载,运算符重载
子类型多态(subtype,运行期)
虚函数
参数多态性(parametric,编译期)
类模板、函数模板
强制多态(coercion,编译器/运行期)
基本类型转换、自定义类型转换
2. 静态多态
编译期,早绑定
函数重载
class A{
void foo();
void foo(int a);
};
3. 动态多态
运行期/晚绑定
虚函数:virtual
修饰
普通函数(非类成员函数)不能是虚函数
静态函数(static)不能是虚函数
构造函数不能是虚函数
因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成时,才会形成虚表指针(重要)
内联函数不能是表现多态时的虚函数
4. 虚析构函数
是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。
Class Base{
public:
Base();
virtual ~Base();
};
class Derived : public Base {
public:
...
};
int main(){
Base *ptr = new Derived();
delete ptr;
ptr = nullptr;
return 0;
}
5. 纯虚函数
一种特殊的虚函数。
在基类中不能对虚函数给出有意义的实现,而是声明为纯虚函数,它的实现留给派生类去做。
Class AbstraceBase{
virtual void foo(int) = 0;
};
虚函数 vs 纯虚函数
- 类如果声明虚函数,且实现了,哪怕是空实现,则作用就是为了能让这个函数在派生类里被override。这样,编译器就可以使用后期绑定来达到多态了;纯虚函数只是一个接口,是个函数声明而已,要留到子类实现
- 虚函数在派生类可以不重写;纯虚函数必须在子类实现才可以实例化
- 虚函数的类用于实作继承,继承接口的同时也继承了父类的实现。纯虚函数关注的是接口的统一性,实现由子类去完成
- 带纯虚函数的类称为抽象类,不能被实例化。只有继承并实现了纯虚函数,才能使用。派生类继承后,可以继续是抽象类,也可以是普通类。
- 虚基类是虚继承中的基类。
6. 虚函数指针、虚函数表
虚函数指针
在含有虚函数类的对象中,指向虚函数表,在运行时确定
虚函数表
在程序只读数据段
.rodata section
,存放虚函数指针。如果派生类实现了基类的某个虚函数,则在虚表中覆盖原本基类的那个虚函数指针,在编译时根据类的声明创建。
7. 虚继承
用于解决多继承条件下的菱形继承问题
浪费存储空间、存在二义性
底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现。每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用空间)(但虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。
实际上,vbptr指的是虚基类表指针,指向一个虚基类表——记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持公有基类(虚基类)的两份同样拷贝,从而节省了存储空间。
虚继承 vs 虚函数
相同之处
都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)
不同之处
- 虚继承
- 虚基类依旧存在继承类中,之占用存储空间
- 虚基类表存储的是虚基类的相对直接继承类的偏移
- 虚函数
- 虚函数不占用存储空间
- 虚函数表存储的是虚函数地址
- 虚继承
模板类、成员模板、虚函数
- 模板类可以使用虚函数
- 一个类的成员模板(本身是模板的成员函数)不能是虚函数
8. 抽象类、接口类、聚合类
- 抽象类:含纯虚函数
- 接口类:仅含纯虚函数的抽象类
- 聚合类:可以直接访问其成员,具有特殊的初始化语法
- 所有成员都是
public
- 没有定义任何构造函数
- 没有类内初始化
- 没有基类、没有
virtual
函数
- 所有成员都是
第三部分
一、内存分配和管理
1. malloc、calloc、realloc、alloca
malloc
申请指定字节数的内存。申请到的内存中的初始值不确定
calloc
为指定长度的对象,分配能容纳指定个数的内存。申请的内存的每一位(bit)都初始化为0
realloc
更改以前分配的内存长度(增加或减少)
当增加长度是,可能需要将以前分配区的内容移到另一个足够大的区域。而新增的区域内的初始值不确定
alloca
在栈上申请内存。
程序在出栈时,会自动释放内存。但需注意,alloca不具有可移植性,而且在没有传统堆栈的机器上很难实现。
alloca不宜使用在必须广泛移植的程序中。C99中支持变长数组(VLA),可以用来代替alloca。
2. malloc和free
分别用于内存的分配和释放
// 内存申请
char *str = (char*)malloc(100);
assert(str != nullptr);
// 内存释放
free(str);
str = nullptr;
3. new和delete
- new/new[]
- 先底层调用
malloc
分配内存 - 再调用构造函数(创建对象)
- 先底层调用
- delete/delete[]
- 先调用析构函数(清理资源)
- 再底层调用
free
释放空间
- new申请内存时会自动计算所需要的字节数;malloc则需要我们自己输入申请空间的字节数
int main(){
T* t = new T(); // 先分配内存,再构造
delete t; // 先析构,再free
return 0;
}
4. 定位new
placement new
允许我们向new
传递额外的地址参数,从而再预先指定的内存区域创建对象。
// place_addr是一个指针
// initializers提供一个以逗号分割的初始值列表
new(place_addr) type;
new(place_addr) type (initializers);
new(place_addr) type [size];
new(place_addr) type [size] {braced intializer list};
5. delete this 合法吗?
合法,但是:
- 必须保证
this
对象是通过new
(不是new[]
、不是placement new
、不是栈上、不是全局、不是其他对象成员)分配的 - 必须保证调用
delete this
的成员函数是最后一个调用this的成员函数 - 必须保证成员函数的
delete this
后面不再调用this
了 - 必须保证
delete this
后没有人再使用了
总之,delete this
对调用的成员函数有很严格的要求。
class A{
public:
A(){};
void destory(){ delete this;} // 必须显式的调用,进行内存空间释放
private:
~A(){} // 私有函数,只能通过new去动态构造
};
6. 栈上或堆上生成对象
C++中,类对象的建立有两种:
静态建立
如
A a;
由编译器为对象在栈空间分配内存,是通过移动栈顶指针,挪出适当的空间,然后在这片内存空间上调用构造函数,形成一个栈对象。
为什么不把构造函数设置为
private
?构造函数设置为私有,会影响new动态建立对象,因为其第二阶段也会调用构造函数。
只能在
堆
上方法:将析构函数设置为私有
C++是静态绑定语言,编译器管理栈上对象的声明周期。
编译器为类的对象分配栈空间时。会先检查类的析构函数的访问性;
若析构函数不可访问,则不能在栈上创建对象。
缺点:无法解决继承问题
若其作为基类,则析构函数一般要设置为
virtual
,然后在子类重写,以实现多态,所以析构函数不能设置为private。可以将析构函数设置为protected
class A{
protected: // protected,解决继承问题
A(){}
~A(){}
public:
static A* create(){return new A();} // 静态方法,负责创建
void destroy() {delete this;} // 负责释放内存空间,必须最后调用
};
只能在
栈
上方法:将
new
和delete
重载为private在堆上生成对象,使用
new
关键字操作,过程包括两个阶段:- 使用
new
在堆上需找可用内存,分配给对象 - 调用构造函数生成对象
将
new
操作设置为private,那么第一阶段的操作就无法完成,就不能在堆上生成对象- 使用
class A{
private:
void *operator new(size_t){}; // 函数第一个参数和返回值都是固定的
void operator delete(void* ptr){};// 重载new就需要重载delete
public:
A(){}
~A(){}
};
有个问题:
如果既把析构函数设置为private,也将
new
和delete
重载为了private,那么对象会创建在哪里?创建失败?
二、智能指针
1. 标准库
#include <memory>
std::auto_ptr<std::string> ps (new std::string(str));// C++98
// C++ 11中 auto_ptr被弃用
2. shared_ptr
多个智能指针可以共享同一个对象,对象的最末一个拥有者有责任销毁对象,并清理与该对象相关的所有资源。
优点:
支持定制型删除器(
custom deleter
)可防范
Cross-DLL
问题对象在动态链接库DLL中new创建,却在另一个DLL内被delete销毁
自动解除互斥锁
3. Weak_ptr
允许共享但不拥有某个对象,一旦最末一个拥有该对象的智能指针失去了所有权,任何weak_ptr
都会自动成空。
因此,在default
和copy
构造函数之外,weak_ptr
只提供接受一个shared_ptr的构造函数
优点:
可打破环状引用
两个其实已经没有被使用的对象,彼此互指,使之看似还在『被使用』的状态的问题
4. unique_ptr
C++ 11提供的类型,在异常时可以帮助避免资源泄露的智能指针,用于取代auto_ptr
独占式拥有,即一个对象和相应的资源同一时间只被一个pointer
拥有。
一旦拥有者被销毁或设置为empty
,或开始拥有另一个对象,先前拥有的那个对象就会被销毁,响应资源会被释放。
uique_ptr v.s auto_ptr
- auto_ptr可以赋值拷贝,复制拷贝后所有权转移;unique_ptr无赋值拷贝语义,但实现了
move
语义 - auto_ptr对象不能管理数组(析构调用
delete
);unique_ptr可以管理数组(析构调用delete[]
)
5. 强制类型转换
待补充
附录:
C++基础知识复习的更多相关文章
- spring 基础知识复习
spring是一个分层架构,由 7 个定义良好的模块组成.Spring 模块构建在核心容器之上,核心容器定义了创建.配置和管理 bean 的方式. 组成spring框架的每个模块(或组件)都可单独存在 ...
- JavaScript进阶【三】JavaScript面向对象的基础知识复习
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- ZYNQ笔记(0):C语言基础知识复习
ZYNQ的SDK是用C语言进行开发的,C语言可以说是当今理工类大学生的必备技能.我本科学C语言时就是对付考试而已,导致现在学ZYNQ是一脸懵逼.现在特开一帖,整理一下C语言的基础知识. 一.定义 1. ...
- Javaweb基础知识复习------AJAX
AJAX相关知识复习 简而言之,就是可以用AJAX+HTML代替JSP页面,也可以进行异步交互,更新部分界面 Ajax案例 后端代码就是一个servlet文件,前端页面的代码也不是很常用,可以在下面这 ...
- MySQL数据库基础知识复习
现在是2020年寒假,这也是新年写的第一篇博客,用了十几天的时间自学了数据库基础部分,想总结一下得失同时并通过写博客来复习前面学的知识点. 个人: 1.本来是计划一周学完基础部分的178p但没能完成这 ...
- android基础知识复习——RelativeLayout布局属性、背景、半透明设置(XML设置)
转自:http://blog.csdn.net/fansongy/article/details/6817968 复习布局与XML,写了一个空的登录界面.XML的注释我写在当行的后面了.程序运行图: ...
- JAVA基础知识复习小结
集合 Set集合 Set集合的基本特征是元素不允许重复.HashSet不保存元素顺序,LinkedHashSet用链表保持元素的插入顺序,TreeSet可定制排序规则. HashSet的底层是用Has ...
- C++ 基础知识复习(六)
操作系统部分: 79. 操作系统的最小调度单位:线程. 线程thread,进程process.一个进程至少包含一个线程,主线程,main thread. 80. 资源的最小单位是:进程. 81. 进程 ...
- C++ 基础知识复习(五)
UML建模部分 70. 什么是UML: 答: Unified Modeling Language, 统一建模语言,是一种标准的图形化建模语言.是面向对象分析和设计的标准表示. 71. UML有哪些图: ...
- C++ 基础知识复习(三)
43. 继承的几种方式: 答:共有继承public,保护继承protected,私有继承private.其中后两种继承会改变原有的访问级别. 44. 深复制与浅复制: 答:简单理解,深复制自己申请了内 ...
随机推荐
- Hybrid-PSC:基于对比学习的混合网络,解决长尾图片分类 | CVPR 2021
论文提出新颖的混合网络用于解决长尾图片分类问题,该网络由用于图像特征学习的对比学习分支和用于分类器学习的交叉熵分支组成,在训练过程逐步将训练权重调整至分类器学习,达到更好的特征得出更好的分类器的思想 ...
- #点分治,树状数组#洛谷 5311 [Ynoi2011] 成都七中
题目 给你一棵 \(n\) 个节点的树,每个节点有一种颜色,有 \(m\) 次查询操作. 查询操作给定参数 \(l\) \(r\) \(x\),需输出: 将树中编号在 \([l,r]\) 内的所有节点 ...
- 快捷转换/互转 Markdown 文档和 TypeScript/TypeDoc 注释
背景 作为文档工具人,经常需要把代码里面的注释转换成语义化的 Markdown 文档,有时也需要进行反向操作.以前是写正则表达式全局匹配,时间长了这种方式也变得繁琐乏味.所以写了脚本来互转,增加一些便 ...
- 深入理解 C++ 右值引用和移动语义:全面解析
C++11引入了右值引用,它也是C++11最重要的新特性之一.原因在于它解决了C++的一大历史遗留问题,即消除了很多场景下的不必要的额外开销.即使你的代码中并不直接使用右值引用,也可以通过标准库,间接 ...
- Python 布尔类型
布尔值表示两个值之一:True(真)或False(假). 布尔值 在编程中,您经常需要知道一个表达式是否为True或False. 您可以在Python中评估任何表达式,并获得两个答案之一:True或F ...
- C# 项目打包详解--赞
项目打包流程如下: 第一步:项目必须先安装 Microsoft Visual Studio Installer Projects 安装步骤:打开VS-->工具-->扩展和更新--> ...
- oracle database recover database (下篇)
1. recover database 恢复级别一共三个:recover database > recover tablespace > recover datafile ,最高级别 da ...
- 【直播预告】HarmonyOS极客松赋能直播第二期:数据库与网络连接开发
- ArkUI,更高效的框架设计
原文:https://mp.weixin.qq.com/s/uSIzuBby7Z92drNDmejKXw,点击链接查看更多技术内容. 上期文章我们讲到了ArkUI的三大特性,同时提到了Ark ...
- 浅析eTS的起源和演进
原文:https://mp.weixin.qq.com/s/N2RPeboN8Fj0-8wBMZJ-7w,点击链接查看更多技术内容. 引言 Mozilla创造了JS,Microsoft创建了TS,Hu ...