我的C++知识点学习笔记。
C++ Hello World
C++和C的区别
- C++是面向对象的语言,C是面向过程的语言;
- C++引入
new/delete运算符,取代了C中的malloc/free库函数; - C++引入引用的概念,而C中没有;
- C++引入类的概念,而C中没有;
- C++引入函数重载的特性,而C中没有;
面向对象的三大特征
- 封装:封装是面向对象方法的一个重要原则,就是把对象的属性和服务结合成一个独立的系统单位,并尽可能隐蔽对象的内部细节;
- 继承:特殊类的对象用于其一般类的全部属性与服务,称作特殊类对一般类的继承;
-
多态:多态性是指在一般类中定义的属性或行为,被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为;
- 静态多态:编译时的多态,绑定工作在编译连接阶段完成,称为静态绑定,e.g. 函数重载(包括运算符重载);
- 动态多态:运行时的多态,绑定工作在程序运行阶段完成,称为动态绑定,e.g. 虚函数;
C++关键字
const关键字
符号常量
const value_type variable_name = variable_value;
- 符号常量在使用之前一定要首先声明:
- 符号常量不能被赋值:
常对象
const class_name object_name;
- 常对象必须进行初始化,而且不能被更新;
- 在声明常对象时,把
const关键字放在类型名后面也是允许的,不过人们更习惯于把const写在前面; - 基本数据类型的常量也可看作一种特殊的常对象,因此,后面将不再对基本数据类型的常量和类类型的常对象加以区分;
用const修饰的类成员
常成员函数
value_type function_name(variables) const;
const是函数类型的一个组成部分,因此在函数的定义部分也要带const关键字;- 如果将一个对象声明为常对象,则通过该常对象只能调用它的常成员函数,而不能调用其他成员函数(这就是C++从语法机制上对常对象的保护,也是常对象唯一的对外接口方式);
- 无论是否通过常对象调用常成员函数,在常成员函数调用期间,目的对象都被视同为常对象,因此常对象函数不能更新目的对象的数据成员,也不能针对目的对象调用该类中没有用
const修饰的成员函数(这就保证了在常成员函数中不会更改目的对象的数据成员的值); -
const关键字可以用于对重载函数的区分,如果仅以const关键字为区分对成员函数重载,那么通过非const的对象调用该函数,两个重载的函数都可以与之匹配,这时编译器将选择最近的重载函数——不带const关键字的函数:T& operator[](const std::size_t i) { return data_[i]; } const T& operator[](const std::size_t i) const { return data_[i]; }
常数据成员
const value_type variable_name;
static const value_type variable_name;
- 类的成员数据也可以是常量;
- 常数据成员只能通过初始化列表来获得初值;
- 静态常数据成员在类外说明和初始化;
常引用
const value_type& reference_name;
- 常引用所引用的对象不能被更新;
- 如果用常引用作形参,便不会意外地发生对实参的更改;
- 对于在函数中无须改变其值的参数,不宜使用普通引用方式传递,因为那会使得常对象无法被传入,采用传值方式或传递常引用的方式可避免这一问题;
- 对于大对象来说,传值耗时较多,因此传递常引用为宜;
- 复制构造函数的参数一般也宜采用常引用传递;
- 在32位处理器上,小于4字节的数据类型建议传值,在64位处理器上,小于8字节的数据类型建议传值;
delete关键字
enum关键字
enum enum_name {enum_list};
- 枚举类型用于表示可取值有限的变量,方便检查数据合法性;
- 对枚举元素按常量处理,不能对它们赋值;
- 枚举元素具有默认值,默认从0开始,依次加1;
- 可以在声明时另行定义枚举元素的值,之后的值依次加1;
- 枚举值可以进行关系运算,同一类型枚举变量可互相赋值;
- 枚举类型可以隐式转换为整型,整型转换为枚举类型需要进行显式类型转换;
extern关键字
外部变量和外部函数
C++和C的混合编程
使用extern "C"实现C++和C的混合编程
#ifdef __cplusplus
extern "C" {
#endif
void display();
#ifdef __cplusplus
}
#endif
friend关键字
inline关键字
inline value_type function_name(variables) {
// implementation
}
- 内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处;
- 对于一些功能简单、规模较小又使用频繁的函数,可以设计为内联函数;
- 函数实现在头文件中的函数建议定义为内联函数,否则有可能违反单一定义原则(One Definition Rule,ODR),造成未定义行为;
new关键字
static关键字
静态数据成员
- 如果某个属性为整个类所共有,不属于任何一个具体对象,则采用
static关键字来声明为静态成员; - 类属性是描述类的所有对象共有特征的一个数据项,对于任何对象实例,它的属性值是相同的;
- 静态数据成员具有静态生存期;
- 由于静态数据成员不属于任何一个对象,因此可以通过类名对它进行访问,一般的用法是
类名::标识符; - 在类的定义中仅仅对静态数据成员进行引用性声明,必须在命名空间作用域的某个地方使用类名限定定义性声明,这时也可以进行初始化(在类中声明,在类外初始化);
静态函数成员
static value_type function_name(variables);
- 静态函数成员可以直接访问该类的静态数据和函数成员;
- 而访问非静态成员,必须通过对象名;
this关键字
typedef关键字
typedef type_name aliases;
- 用于将一个标识符声明成某个数据类型的别名,然后将这个标识符当做数据类型使用;
- 新类型名表中可以有多个标识符,它们之间以逗号分隔;
using关键字
virtual关键字
虚函数
虚函数是动态绑定的基础。虚函数必须是非静态的成员函数。虚函数经过派生之后,在类族中就可以实现运行过程中的多态。
一般虚函数成员
virtual value_type function_name(variables);
- 虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候;
-
运行过程中的多态需要满足三个条件:
- 赋值兼容原则;
- 虚函数;
- 由成员函数来调用或者是通过指针、引用来访问虚函数;
- 虚函数一般不声明为内联函数,因为对虚函数的调用需要动态绑定,而对内联函数的处理是静态的,所以虚函数一般不能以内联函数处理。但将虚函数声明为内联函数也不会引起错误;
虚析构函数
virtual ~ClassName();
纯虚函数和抽象类
纯虚函数
virtual value_type function_name(variables) = 0;
- 纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要给出各自的定义;
- 声明为虚函数之后,基类中就可以不再给出函数的实现部分,纯虚函数的函数体由派生类给出;
- 基类中仍然允许对纯虚函数给出实现,但即使给出实现,也必须由派生类覆盖,否则无法实例化;
抽象类
- 带有纯虚函数的类是抽象类;
- 抽象类不能实例化;
类型转换操作符
显式类型转换:
value_type(expression); // C++风格
(value_type)expression; // C风格
类型转换操作符:
static_cast<value_type>(expression);
dynamic_cast<value_type>(expression);
const_cast<value_type>(expression);
reinterpret_cast<value_type>(expression);
区别辨析
结构体struct和类class
- 结构体是一种特殊形态的类,它和类一样,可以有自己的数据成员和函数成员,可以有自己的构造函数和析构函数,可以控制访问权限,可以继承,支持包含多态等,二者定义的语法形式也几乎一样;
-
结构体和类的唯一区别在于,结构体和类具有不同的默认访问控制属性:
- 在类中,对于未指定访问控制属性的成员,其访问控制属性为私有类型
private; - 在结构体中,对于未指定任何访问控制属性的成员,其访问控制属性为公有类型
public;
- 在类中,对于未指定访问控制属性的成员,其访问控制属性为私有类型
- C++引入结构体是为了保持和C程序的兼容性;
结构体struct和联合体union
- 联合体是一种特殊形态的类,它可以有自己的数据成员和函数成员,可以有自己的构造函数和析构函数,可以控制访问权限;
- 与结构体一样,联合体也是从C语言继承而来的,因此它的默认访问控制属性也是公共属性的;
- 联合体的全部数据成员共享同一组内存单元;
-
联合体在存储时具有两种存储形式,具体采用哪种存储形式由处理器决定:
- 大端模式(big endian):高位字节保存在内存中的低位地址,低位字节保存在内存中的高位地址,这种存储方式符合人的正常思维习惯;
- 小端模式(little endian):低位字节保存在内存中的低位地址,高位字节保存在内存中的高位地址,这种存储方式有利于计算机处理;
#define和const
const和constexpr
size_t和int
typedef unsigned int size_t; // 32位
typedef unsigned long size_t; // 64位
与int固定四个字节不同,size_t的取值范围是目标平台下最大可能的数组尺寸,一些平台下size_t的范围小于int的正数范围,又或者大于unsigned int,使用int既有可能浪费,又有可能范围不够大。
NULL和nullptr
为解决NULL代指空指针存在的二义性问题,在C++11中引入nullptr关键字来代指空指针:
NULL // 0
nullptr // 空指针,C++11引入
C++11
关于嵌套模板中相邻的右尖括号
C++11支持嵌套模板中相邻的右尖括号写作>>:
vector<vector<int>>
早期C++标准要求嵌套模板中相邻的右尖括号写作> >:
vector<vector<int> >
其原因是为了避免与输入流运算符>>混淆,否则会报错:
'>>' should be '> >' within a nested template argument list.
关于auto和decltype
auto:让编译器在编译期就推导出变量的类型,可以通过=右侧的类型推导出变量的类型;decltype:相对于auto用于推导变量类型,而decltype则用于推导表达式类型,这里只用于编译器分析表达式的类型,表达式实际不会进行运算;
constexpr关键字
default关键字
C++11支持使用default关键字生成默认函数:
= default
delete关键字
C++11支持使用delete关键字禁用默认函数:
= delete
enum关键字
为了避免名称冲突,C++11引入了枚举类:
enum class enum_name {enum_list};
explicit关键字
C++11支持使用explicit关键字显式声明构造函数和转换操作符,必须要显式调用,避免隐式类型转换。
final关键字
C++11支持使用final关键字声明虚函数不能在派生类中被覆盖或者声明类不能被继承,否则编译器会报错。
for循环
C++11支持基于范围的for循环:
vector<int> vec;
for (int i : vec) {}
string str;
for (char c : str) {}
noexcept关键字
C++11支持使用noexcept关键字声明函数不会抛出任何异常。
override关键字
C++11支持使用override关键字声明派生类中的虚函数覆盖了基类中的虚函数。
using关键字
C++11支持使用using关键字指定别名,作用与typedef关键字相同:
typedef type_name aliases;
using alias = type_name;
关于智能指针
C++11引入智能指针来实现对于堆内存的自动管理。
使用时需要包含头文件:
#include <memory>
std::auto_ptr:自动指针,C++11弃用,C++17移出;std::shared_ptr:共享指针,底层使用引用计数,存在循环引用问题;std::unique_ptr:独享指针,建议优先使用,避免循环引用,性能开销更低;std::weak_ptr:弱共享指针,不会引起引用计数变化,不单独使用,结合共享指针使用,用于解决循环引用问题;
共享指针std::shared_ptr
-
初始化方法,以指向
int类型的共享指针为例:// 空共享指针,初始引用计数为0 std::shared_ptr<int> sp1; std::shared_ptr<int> sp1(nullptr); sp1.reset(new int(10)); // 赋值构造 std::shared_ptr<int> sp2(new int(10)); // 建议使用,会使用重载后的new运算符 std::shared_ptr<int> sp2 = std::make_shared<int>(10); // 辅助构造函数,使用默认的new运算符 // 拷贝构造 std::shared_ptr<int> sp3(sp2); std::shared_ptr<int> sp3 = sp2; // 移动构造 std::shared_ptr<int> sp4(std::move(sp3)); std::shared_ptr<int> sp4 = std::move(sp3);同一个裸指针不能赋值给多个共享指针,否则会导致双重释放(double free),造成未定义行为。
-
共享指针辅助构造函数
make_shared()原型声明:template <class T, class... Args> shared_ptr<T> make_shared(Args&&... args); -
共享指针辅助构造函数
allocate_shared()原型声明:template <class T, class Alloc, class... Args> shared_ptr<T> allocate_shared(const Alloc& alloc, Args&&... args);使用指定的堆内存管理器分配内存空间。
-
常用成员函数原型声明:
void swap(std::shared_ptr& x) noexcept; // 交换指针内容 void reset() noexcept; // 重置指针为空指针 template <class U> void reset(U* p); // 重置指针为指定指针,要求U类型指针能够隐式转换为T类型指针 element_type* get() const noexcept; // 返回裸指针 long int use_count() const noexcept; // 返回引用计数 bool unique() const noexcept; // 判断是否独享
独享指针std::unique_ptr
-
初始化方法,以指向
int类型的独享指针为例:// 空独享指针 std::unique_ptr<int> up1; std::unique_ptr<int> up1(nullptr); up1.reset(new int(10)); // 赋值构造 std::unique_ptr<int> up2(new int(10)); std::unique_ptr<int> up2 = std::make_unique<int>(10); // 辅助构造函数 // 拷贝构造:禁止 // 移动构造 std::unique_ptr<int> up3(std::move(up2)); std::unique_ptr<int> up3 = std::move(up2); -
独享指针禁止拷贝构造,拷贝构造函数原型声明:
// 使用delete关键字禁止编译器使用拷贝构造函数 std::unique_ptr(const std::unique_ptr&) = delete; // 拷贝构造无法编译通过 // std::unique_ptr<int> up4(up3); // std::unique_ptr<int> up4 = up3; -
独享指针辅助构造函数
make_unique()原型声明:// C++14引入 template <class T, class... Args> unique_ptr<T> make_unique(Args&&... args); // 在C++11中的实现 template <typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); } -
常用成员函数原型声明:
element_type* get() const noexcept; // 返回裸指针 element_type* release() noexcept; // 返回裸指针,重置指针为空指针,释放对原内存的所有权,但是不释放原内存 void reset(element_type* p) noexcept; // 重置指针为指定指针,释放原内存 void swap(std::unique_ptr& x) noexcept; // 交换指针内容
弱共享指针std::weak_ptr
-
初始化方法,以指向
int类型的弱共享指针为例:// 空弱共享指针 std::weak_ptr<int> wp1; std::weak_ptr<int> wp1(nullptr); wp1.reset(); // 赋值构造:禁止 // 拷贝构造 std::weak_ptr<int> wp2(wp1); // 移动构造 std::weak_ptr<int> wp3(std::move(wp2)); std::weak_ptr<int> wp3 = std::move(wp2); // 通过共享指针构造 std::shared_ptr<int> sp(new int(10)); std::weak_ptr<int> wp4(sp); - 弱共享指针禁止赋值构造,因为不拥有对内存的所有权,不影响生命周期,是观察者;
-
常用成员函数原型声明:
void swap(std::weak_ptr& x) noexcept; // 交换指针内容 void reset() noexcept; // 重置指针为空指针 long int use_count() const noexcept; // 返回引用计数 bool expired() const noexcept; // 判断是否过期(指针为空,或者指向的堆内存已经被释放) std::shared_ptr<element_type> lock() const noexcept; // 如果未过期,返回智能指针,如果已过期,返回空智能指针没有重载
*和->运算符,只能访问所指的堆内存,而无法修改它;
关于lambda表达式
lambda表达式是一种匿名函数,语法形式如下:
[capture](params) opt -> ret { body; };
-
capture为捕获列表:[]:不捕获任何变量;[&]:捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获);[=]:捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获);[=,&foo]:按值捕获外部作用域中所有变量,并按引用捕获foo变量;[bar]:按值捕获bar变量,同时不捕获其他变量;[this]:捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限,如果已经使用了&或者=,就默认添加此选项,捕获this的目的是可以在lamda表达式中使用当前类的成员函数和成员变量;
params为参数列表,与普通函数的定义相同,如果不需要传参可以省略;-
opt为函数选项,可以省略,如果使用,参数列表的括号不能省略:noexcept:函数体不会抛出任何异常;throw():指定函数体可以抛出的异常类型;
ret为返回值类型,如果lambda表达式内部只有一个return语句,或者没有返回值,则编译器可以自动推断出返回值类型,可以省略;body为函数体,与普通函数的定义相同;- 可以使用
std::function和std::bind来存储和操作lambda表达式;
参考
- 《C++语言程序设计》
- cplusplus
- 校招C++大概学习到什么程度?-程序员内功修炼的回答-知乎
- C++教程-菜鸟教程
- C++入门教程-C语言中文网
- C++11教程-C语言中文网
- 终极C++避坑指南-鹅厂架构师的文章-知乎
- 是C++的发展进入了邪路,还是我写代码的姿势不正确?-真重的回答-知乎
- c++中函数参数里,是否能用const reference的地方尽量都用?-吴咏炜的回答-知乎
- 传值,传地址,传引用的效率区别-博客园
- 值传递、指针传递、引用传递:深入理解函数参数传递方式-CSDN博客
- extern “C”-C语言中文网
- inline和ODR-CSDN博客
- 为什么C++实现多态必须要虚函数表?-Pluveto的回答-知乎
- C++强制类型转换运算符(static_cast、reinterpret_cast、const_cast和dynamic_cast)-C语言中文网
- C++类型转换:强制类型转换和隐式类型转换-C语言中文网
- C++数据类型(强制)转换详解-C语言中文网
- C++是不是结构体都可以用类来表示?有了类,还有使用结构体的必要吗-南山烟雨珠江潮的回答-知乎
- 共用体的大端模式和小端模式1-CSDN博客
- 共用体的大端模式和小端模式2-CSDN博客
- C++11 constexpr:验证是否为常量表达式-C语言中文网
- C++11 constexpr和const的区别详解-C语言中文网
- size_t和int1-CSDN博客
- size_t和int2-CSDN博客
- NULL和nullptr-CSDN博客
- 嵌套模板中的»1-Stack Overflow
- 嵌套模板中的»2-Stack Overflow
- C++11新特性,所有知识点都在这了!-程序喵大人的文章-知乎
- C++11 auto和decltype关键字-C语言中文网
- C++返回值类型后置(跟踪返回值类型)-C语言中文网
- C++ =default和=delete的用法-C语言中文网
- C++随笔:关键字 =default 和 =delete-灵知子的文章-知乎
- default和delete-CSDN博客
- C++里写一个函数体为空的构造函数和构造函数=default这两种写法有何区别?-吴咏炜的回答-知乎
- C++ explicit用法详解-C语言中文网
- C++ explicit关键字-漠空的文章-知乎
- C++ override和final的用法-C语言中文网
- C++ noexcept的用法-C语言中文网
- C++11使用using定义别名(替代typedef)-C语言中文网
- C++中using的三种用法-算法集市的文章-知乎
- c++是否应避免使用普通指针,而使用智能指针(包括shared,unique,weak)?-张小方的回答-知乎
- 现代C++:一文读懂智能指针-FOCUS的文章-知乎
- C++智能指针-小小将的文章-知乎
- C++11智能指针丨巨巨巨详细-Phoenix Studio的文章-知乎
- 如何看待无脑shared_ptr的行为?-南山烟雨珠江潮的回答-知乎
- C++11 shared_ptr智能指针-C语言中文网
- C++11 shared_ptr(智能指针)详解-C语言中文网
- C++11 unique_ptr智能指针详解-C语言中文网
- C++11 weak_ptr智能指针-C语言中文网
- C++ std::weak_ptr智能指针详解-C语言中文网
- std::unique_ptr release的使用-博客园
- C++11 lambda匿名函数用法详解-C语言中文网
- C++11 lambda表达式精讲-C语言中文网
- C++11 Lambda表达式(匿名函数)详解-C语言中文网