C++基础与与深度解析
文章平均质量分 83
深蓝学院C++基础与深度解析课程笔记
Teamo1996
这个作者很懒,什么都没留下…
展开
-
第一章:C++基础
什么是C++C++是一门比较流行的编程语言,同时也是C语言的拓展,它继承了C语言的特性,关注程序的性能,主要包括以下三个方面:与底层硬件紧密结合对对象生命周期的精确控制Zero-overhead Abstraction同时C++还引入了大量的特性,便于工程实践,主要包括以下两个方面:三种编程范式:面向过程、面向对象,泛型函数重载、异常处理、引用C++的开发环境与相关工具C++的编译/链接模型...原创 2021-07-09 03:47:56 · 145 阅读 · 0 评论 -
第二章:C++初探
从Hello Word出发了解C++对于一门语言的入门,最直观的方法就是从一个Hello World程序出发,来了解这门语言的基本结构#include <iostream>int main(int argc,char ** argv) { std::cout << "Hello, World!" << std::endl; return 0;}从这个最简单的代码出发,我们可以引出C++中一个非常重要的定义函数。函数是一段能够被反复调用的代码,原创 2021-07-14 20:58:12 · 164 阅读 · 0 评论 -
第三章:对象与基本类型(一)
初始化与赋值语句初始化/赋值语句是程序中最基本的操作,其功能是将某个值与一个对象关联起来,典型的例子如下int x = 10; // 初始化x = 20; // 赋值其中值的范围比较广泛,可以是字面值,对象(变量或常量)所表示的值等 。而对于对象我们使用标识符来描述,标识符分为:常量、变量和引用等。值和对象都是有着对应的类型的,比如10也有着类型。相比起赋值语句,初始化语句需要额外进行一些操作,包括:在内存中开辟空间,保存对应的数值在编译器中构造符号表,将标识符与相关内存空间关联起原创 2021-07-15 04:52:03 · 248 阅读 · 0 评论 -
第三章:对象与基本类型(二)
复合类型:指针及引用指针指针是一种复合类型,它本身也是一个对象,在其中存储了其他对象在内存中的首地址,从而建立了从内存到内存之间的指向关系,所以被称为指针。我们可以使用以下的图来形象地理解指针指针有两个非常重要的特点:指针可以指向不同的对象,比如int x = 42;int y = 56;int *p = &x;p = &y;指针具有相同的尺寸:因为指针内部存储的是内存地址,而对于特定的机器来说,它能取到的所有可能的内存地址的个数是固定的。比如,对用通常的64位机,它原创 2021-07-16 06:05:05 · 161 阅读 · 0 评论 -
第三章:对象与基本类型(三)
常量与常量表达式常量与变量相对,表示不可修改的对象,我们可以使用const来声明常量,比如 const int x = 4;常量是一个编译期的概念,它并没有底层的硬件支持,本质上和变量没有区别,它由编译器来保证不可修改的性质。编译器利用常量来防止非法操作,比如const int x = 4;if(x = 3){ // 这里不小心少写了一个=}优化程序逻辑,比如const int x = 4;{//....}int y = x + 1; // 编译器在这里可以进行优化,原创 2021-07-20 20:41:47 · 59 阅读 · 0 评论 -
第三章:对象与基本类型(四)
类型别名我们可以为类型引入别名,从而引入特殊的含义或者便于使用,比如size_t。通常来说,引入类型别名有两种方式typedeftypedef int MyInt;using(从C++11开始)using MyInt = int;使用using要比typedef更加优化易读,比如 typedef char MyCharArr[4]; // 读起来比较混乱 using MyCharArr = char[4]; // 读起来清晰明了我们需要区分类型别名与指针、引用之间的关系应该将原创 2021-07-21 01:39:52 · 66 阅读 · 0 评论 -
第四章:数组、vector与字符串(一)
数组数组基础数组是将一到多个相同类型的对象串联到一起所组成的类型,比如int b[10]; // 类型为int[10]注意,这里面10是类型的一部分,所以int b[变量]是不合法的,数组的声明中只接受可以转换为大于0的整数的常量表达式数组有两种初始化方式:缺省初始化,会将数组中的每一个元素都按照缺省初始化的方式进行初始化,局部变量初始化为随机值,全局变量、线程相关变量和静态变量初始化为0。比如int b[10];聚合初始化,比如int b[3] = {1,2,3}; //初原创 2021-07-21 04:39:49 · 277 阅读 · 0 评论 -
第四章:数组、vector与字符串(二)
数组其他操作与数组相关的还有一些其他的操作求数组中元素的个数sizeofint a [3];sizeof(a)/sizeof(int); // 使用sizeof数组没有退化成指针,获取到的是数组所占用的内存大小std::sizeint a[3];std::size(a); // 直接获取数组内元素的个数(c)end - (c)beginint a[3];std::cend(a) - std::cbegin(a); // 直接获取数组内元素的个数以上原创 2021-07-21 21:31:22 · 93 阅读 · 0 评论 -
第四章:数组、vector与字符串(三)
vectorvector是C++标准库中定义的一个类模版,它可以实现类似数组的功能。std::vector<int> x;与内建数组相比,vector更侧重于易用性而不是性能。比如vector是可以复制的,比如std::vector<int> x;std::vector<int> y;y = x;同时,它也支持在运行期动态改变大小,比如std::vector<int> x; // 在初始化时候尺寸不定vector的性能要比内建原创 2021-07-22 01:36:28 · 335 阅读 · 0 评论 -
第五章:表达式基础与详述(一)
表达式基础引言表达式由一到多个操作数组成,可以求值并通常会返回求值结果,比如x = 3 // 两个操作数,返回求值结果xfun() // 函数调用也是表达式,但有些函数没有返回值,也就是不返回求值结果最基本的表达式为变量和字面值,但通常来说,我们会在表达式中引入操作符(运算符),操作符具有以下特性可以接收几个操作数:一元、二元、三元可以接收的操作数的类型,其中可能会发生类型转换3 + 2.3 // 可以接收的操作数是左值还是右值操作符构成的表达式的结果的类型操作符原创 2021-07-22 22:35:47 · 140 阅读 · 0 评论 -
第五章:表达式基础与详述(二)
表达式详述算术操作符算术操作符共分为以下三个优先级+,-(一元)int x = 3;+x;-x;*,/,%+,-(二元)int x = 3;int y = 5;x + y;x - y;以上的算术操作符均为左结合。通常来讲,以上操作符的操作数和结果均为算术类型的右值,但加减法和一元+可接收指针类型的操作数。int a[3];int * ptr = a;ptr = ptr + 1;ptr = ptr - 1;const auto & x = a; //原创 2021-07-23 02:58:54 · 75 阅读 · 0 评论 -
第五章:表达式基础与详述(三)
表达式详述自增与自减运算符自增与自减运算符:++,--,它最基本的含义与x = x +/- 1等价。在使用自增和自减运算符时我们需要注意以下几点自增和自减运算符分为前缀和后缀两种int x = 3;int y ;int z ;y = x++; // y = 3,x = 4y = ++x; // y = 4,x = 4后缀自增运算符返回x变化之前的值,前缀自增运算符返回x变化之后的值,对于自减运算符也是类似的自增和自减运算符是右结合的自增和自减运算符的操作数为左值,原创 2021-07-23 03:56:29 · 141 阅读 · 0 评论 -
第六章:语句(一)
语句基础语句在C++之中主要分为以下几类:表达式语句:表达式后加分号,对表达式求值后丢弃,可能产生副作用,比如int x;x = 3;求值结果返回x,副作用为对x的值进行了修改空语句:仅包含一个分号的语句,可能与循环一起工作复合语句(语句体):由大括号组成,无需在结尾加分号,形成独立的域(语句域)注意,分号的目的是标识语句的结束,因为C++中换行并不代表语句的结束。而大括号本身也是用来标识语句的开始和结束的,所以不需要再加分号复合语句形成了一个独立的域,不同域的相同原创 2021-07-27 22:10:55 · 128 阅读 · 0 评论 -
第六章:语句(二)
循环语句whilewhile的具体语法如下图所示while语句的处理逻辑分为以下三步:判断条件是否满足,如果不满足则跳出循环如果条件满足则执行循环体执行完循环体后转向步骤1int x =3;while(x){ std::cout << x << std::endl; --x;}注意,在while条件部分不包含额外的初始化内容do-whiledo-while的具体语法如下图所示注意do-while结尾处要有分号,表示一条语句的结束do-原创 2021-07-28 02:07:25 · 78 阅读 · 0 评论 -
第七章:函数(一)
函数基础函数:封装了一段代码,这段代码可以在执行过程中被反复调用。函数包含了两大部分:函数头,函数头中主要分为以下几部分函数名称:标识符,用于后续调用形式参数:代表函数的输入参数返回类型:函数执行后返回的结果类型函数体:语句快,包含了具体的计算逻辑关于函数我们需要了解两个非常重要的概念:函数的声明和函数的定义函数的声明只包括函数头,不包含函数体,通常置于头文件中函数的声明可以出现多次,但函数定义通常只能出现一次关于函数调用我们需要注意以下几点:函数调用需要提供函数名与实原创 2021-07-28 23:24:39 · 174 阅读 · 0 评论 -
第七章:函数(二)
函数重载与重载解析函数重载是指使用相同的函数名定义多个函数,每个函数具有不同的参数列表int fun(int x){ return x + 1;}double fun(double x){ return x + 1;}注意,不能基于不同的返回类型进行重载编译器在根据实参和函数名称选择正确的函数版本完成调用时需要进行很多工作,具体的请参考Calling Functions: A Tutorial,这里我们主要关注以下两个最重要的步骤:名称查找:根据函数名称找到候选的原创 2021-07-29 05:04:20 · 149 阅读 · 0 评论 -
第八章:深入IO
IOStream概述C++中IOstream采用的是流式I/O而非记录I/O,但可以在此基础上引入结构信息(在上层调用的时候可以按照结构来进行传递,很好地划分了底层实现与上层应用),两种I/O的主要区别如下:流式I/O:认为输入输出过程中的每一个字符之间并没有关联,是单独的数据,通过数据流来进行连续的输入输出记录I/O:比如数据库的条目,按照结构来组织输入输出的数据IOStream主要处理以下问题:表示形式的变化:使用格式化/解析在数据内部表示与字符序列间进行转换 char原创 2021-07-10 05:48:13 · 121 阅读 · 0 评论 -
第九章:动态内存管理(一)
动态内存基础在讨论动态内存之前,首先需要明确两个概念:栈内存:有更好的局部性,在栈中构造的局部对象和局部变量在内存地址上一般都是相邻的,方便读取。同时在栈中构造的对象会在程序返回或者局部变量超出其定义域时被自动销毁,不需要程序员关注内存的管理。堆内存:堆内存可以在运行期动态拓展,比如STL容器中的Vector的自动拓展大小本质上就利用了堆内存的特性。同时堆内存需要进行显式的释放,需要程序员花费更多的精力来关注内存的管理,但也使得我们可以灵活地控制一个对象的生命周期。注意这里我们提到的是特点而不原创 2021-07-10 05:45:32 · 90 阅读 · 0 评论 -
第九章:动态内存管理(二)
智能指针使用new和delete会产生问题:内存所有权不清晰,容易产生不销毁或者多销毁的情况,导致内存泄漏或者程序崩溃。为了解决这个问题,C++提供了智能指针,智能指针本质上是一个抽象数据类型,它能够提供析构函数,只要智能指针被销毁,它所对应的内存就会被释放掉,不需要显式的调用delete。C++提供了以下几种智能指针:auto_ptr(C++17删除)shared_ptr:基于引用计数的共享内存解决方案,在shared_ptr内部维护了一个共享的引用计数,每当同一块内存被一个shared_原创 2021-07-18 22:48:41 · 119 阅读 · 0 评论 -
第十章:序列与关联容器(一)
容器概述容器是一种特殊的类型,其对象可以放置其他类型的对象(元素)。通常来讲,容器需要支持以下的操作:对象的添加对象的删除对象的索引对象的遍历以上所述只是通常的情况,有一些容器只支持其中一部分操作有多种算法可以用来实现容器,每种方法各有利弊。C++中的容器可以大致分为以下四类:序列容器:其中的对象有序排列,使用整数值进行索引关联容器:其中对象的顺序并不重要,使用键来进行索引适配器:调整原有容器的行为,使得其对外展现出新的类型、接口或者返回新的元素生成器:构造元素序列关于容原创 2021-07-27 00:45:21 · 229 阅读 · 0 评论 -
第十章:序列与关联容器(二)
关联容器关联容器与顺序容器最大的区别是关联容器的索引(键)不需要一定是从0开始递增的整数,还可以是其他类型,比如int main(int argc, char * argv[]) { std::map<char,int> m{{'a',3},{'b',4}}; std::cout << m['a'] << std::endl;}与顺序容器类似,C++也提供了多种关联容器的实现方法set/multisetmap/multimapunord原创 2021-07-31 03:39:14 · 127 阅读 · 0 评论 -
第十章:序列与关联容器(三)
适配器与生成器类型适配器和接口适配器这里主要讨论两种类型适配器basic_string_view(C++17):主要的目的是将不同类型的字符串统一起来,并提供了统一的接口,比如void fun(std::string_view str){ std::cout << str[0] << std::endl;}int main(int argc, char * argv[]) { fun("1,2,3,4,5"); fun(std::string(原创 2021-08-01 02:04:13 · 123 阅读 · 0 评论 -
第十一章:泛型算法与Lambda表达式(一)
泛型算法泛型算法是可以支持多种类型的算法,这里主要讨论C++标准库中定义的算法,包括algorithmnumericranges这里出现了一个问题,那就是为什么要引入泛型算法而不采用方法的形式,主要原因有以下两点这里的方法指的是类的成员函数内建数据类型不支持方法泛型算法内部的计算逻辑一般来说是存在相似性的,可以避免在方法中重复实现相同的逻辑这里的相似性一般使用模版来实现那么泛型算法如何实现支持多种类型呢?C++给出的办法是使用迭代器来作为算法与数据之间的桥梁,比如原创 2021-08-06 22:21:22 · 267 阅读 · 0 评论 -
第十一章:泛型算法与Lambda表达式(二)
bind在泛型算法中,很多算法允许通过可调用对象来自定义计算逻辑的细节,比如transform、copy_if和sort等可调用对象分为以下几种:函数指针:概念直观,但定义位置受限(不能定义在函数内部)bool MyPredict(int val){ return val > 3;}int main() { std::vector<int> x{1,2,3,4,5,6,7,8,9,10}; std::vector<int> y; s原创 2021-08-21 15:00:25 · 168 阅读 · 0 评论 -
第十二章:类(一)
结构体与对象聚合结构体是一种对基本数据结构进行扩展,并将多个对象放置在一起来视为一个整体的类型,比如struct Str{ int x; int y;};关于结构体,我们需要注意以下几点结构体的声明与定义(注意定义后面要跟分号来表示结束)struct Str; // 声明struct Str{ // 定义 int x; int y;};仅有声明的结构体是不完整的类型(incomplete type),不完整类型可以用来定义对应的指针,但不能用原创 2021-08-29 16:21:43 · 119 阅读 · 0 评论 -
第十二章:类(二)
构造函数构造函数是一类特殊的成员函数,它是在构造对象时调用的成员函数。构造函数的名称与类名相同,无返回类型,可以包含多个版本(重载)class Str{public: Str(){ std::cout << "Constructor is called " << std::endl; } Str(int input){ x = input; }private: int x;};在C++11之后原创 2021-09-04 02:41:36 · 96 阅读 · 0 评论 -
第十二章:类(三)
拷贝赋值与移动赋值函数根据C++对于构造和赋值的区分衍生出了两类不同的函数,构造函数和赋值函数,接下来我们关注与赋值操作相关的两个函数:拷贝赋值函数和移动赋值函数(opreator =)class Str{public: Str() = default; Str(const Str & val) = default; Str(Str&& x) noexcept = default; Str& operator = (const Str原创 2021-09-04 15:42:59 · 77 阅读 · 0 评论 -
第十二章:类(四)
字面值类字面值类是可以构造编译期常量的抽象数据类型,比如class Str{public:private: int x = 3;};constexpr Str a;关于字面值类,我们需要注意以下几点字面值类的数据成员需要是字面值类型如果不使用默认生成的构造函数,那么字面值类需要提供constexpr/consteval构造函数(小心使用consteval)class Str{public: constexpr Str(int val) :x(val原创 2021-09-04 18:16:51 · 57 阅读 · 0 评论 -
第十三章:类的进阶(一)
运算符重载概述在C++中,我们可以通过operator关键字引入重载函数来为类定义一些特殊的运算struct Str{ int val = 3;};auto operator + (Str x, Str y){ Str z; z.val = x.val + y.val; return z;}int main() { Str x; Str y; Str z = x + y; std::cout << z.val &l原创 2021-09-12 16:36:25 · 114 阅读 · 0 评论 -
第十三章:类的进阶(二)
类的继承类可以通过继承或者派生来引入“是一个”的关系struct Base{};struct Derive : public Base{ };关于继承,我们需要关注以下几点通常使用public继承(struct缺省情况下使用public继承而class缺省情况下使用private继承)继承部分不是类的声明可以使用基类的指针或者引用来指向派生类的对象 int main() { Derive d; Base * ptr = &d; Base &原创 2021-09-18 04:43:59 · 114 阅读 · 0 评论 -
第十三章:类的进阶(三)
类继承的补充首先我们来讨论一下public、protected和private继承之间的区别,可以参考这里public inheritance makes public members of the base class public in the derived class, and the protected members of the base class remain protected in the derived class.protected inheritance makes原创 2021-09-25 03:36:32 · 173 阅读 · 0 评论 -
第十四章:模版(一)
函数模版我们可以使用template关键字引入模版,比如template <typename T>void fun(T input){ }typename关键字可以替换为class,含义相同函数模版中包含了两对参数:函数形参/实参;模版形参/实参函数模版需要进行显式实例化,比如fun<int>(3);关于显式实例化,需要知道以下几点:实例化会使得编译器产生相应的函数(函数模版并非函数,不能调用)模版函数在编译期的两阶段处理模版语法检查原创 2021-10-08 20:22:02 · 82 阅读 · 0 评论 -
第十四章:模版(二)
类模版与成员函数模版我们可以使用template来引入类模版;template <typename T>class B{};关于类模版,我们需要注意以下几点类模版的声明与定义需要满足翻译单元级别的一处定义原则成员函数只有在调用时才会被实例化类模版名称在模版类内或者模版成员函数内可以进行简写template <typename T>class B{public: auto fun(){ return B{}; // 编译器自动视为原创 2021-10-16 20:12:37 · 80 阅读 · 0 评论 -
第十四章:模版(三)
Concepts模版存在一些固有的问题:我们并没有办法对模版参数引入相应的限制参数是否可以正常工作,通常需要阅读代码进行理解编译报错的友好性较差(vector<int &>)为了解决这个问题,在C++20引入了Concepts:编译器谓词,他可以基于给定的输入,返回true或false,他可以与constraints(require从句)一起使用来限制模版参数。通常我们将constraints置于表示模版形参的尖括号后面进行限制template <typename T原创 2021-10-16 21:42:47 · 110 阅读 · 0 评论 -
第十四章:模版(四)
数值模版参数与模版模版参数模版可以接收(编译期常量)数值作为模版参数template <int a>template <int a>template <typename T,T val>template <typename T,T val>int fun(int x){ return x + val;}(C++17)template <auto a>template <auto a>void f原创 2021-10-17 03:14:46 · 147 阅读 · 0 评论 -
第十五章:元编程(一)
元编程的引入在引入元编程之前我们需要回顾下泛型编程,泛型编程是使用一套代码来处理不同的类型。但对于一些特殊的类型需要引入额外的处理逻辑,也就是在编译期引入操作程序的程序-元编程。或者更为通俗的讲,可以将元编程理解为编译期计算。我们可以使用编译期计算来辅助运行期计算,但需要在概念上着重强调两点这种辅助并不是简单地将整个运算一分为二我们需要详细分析哪些内容可以放到编译期,哪些需要放到运行期。如果某种信息需要在运行期确定,那么通常无法利用编译期计算元程序的形式通常有以下几种:模板, constexp原创 2021-10-29 18:26:16 · 333 阅读 · 0 评论 -
第十五章:元编程(二)
循环语句的编写方式首先我们来看一个简单的例子:计算二进制中包含 1 的个数template <int x>constexpr auto fun = (x % 2) + fun<x/2>;template<>constexpr auto fun<0> = 0;在编译期我们通常会使用递归来实现循环。此外,任何一种分支代码的编写方式都对应相应的循环代码编写方式。接下来我们再来看一个例子:使用循环处理数组并获取数组中 id=0,2,4,6… 的元原创 2021-10-29 23:54:51 · 80 阅读 · 0 评论 -
第十五章:元编程(三)
减少实例化技巧最后我们来讨论一下减少实例化的技巧,首先我们需要明白我们为什么要减少实例化:提升编译速度,减少编译所需内存。减少实例化通常使用以下技巧:提取重复逻辑以减少实例个数conditional使用时避免实例化// 修改前using Res = std::conditional_t<false, std::remove_reference_t<int&>,原创 2021-10-30 02:49:54 · 143 阅读 · 0 评论 -
第十六章:其他工具与技术(一)
异常处理异常处理用于处理程序在调用过程中的非正常行为:传统的处理方法:传返回值表示函数调用是否正常结束C++中的处理方法:通过关键字try/catch/throw引入异常处理机制void f1(){ throw 1;}void f2(){ f1();}void f3(){ f2();}int main(){ try{ f3(); } catch(int){ std::cout << "exc原创 2021-10-30 17:47:04 · 115 阅读 · 0 评论 -
第十六章:其他工具与技术(二)
枚举枚举(enum) 是一种取值受限的特殊类型,具体的参考这里enum Color{ Red, // 全局作用域 Green, Yellow;};int main(){ Color x = Red;}关于枚举,我们需要知道以下几点:分为无作用域枚举与有作用域枚举( C++11 起)两种enum class Color{ Red, // 有作用域枚举 Green, Yellow;};int main(){ Color x原创 2021-10-30 23:43:45 · 98 阅读 · 0 评论