💡
不是C++保姆式教学,更像是C++保姆式技巧合集,不用完全按顺序看,点击目录可跳转大标题,点击大标题可以跳转视频教学(未完待续)
 
目录导航:(点击即可跳转)
 

C++是如何工作的20:21(点击跳转视频)

c++源文件——》编译器(包含链接器)——》可执行程序或者某种库(二进制)
#号后面都是预处理的内容
预处理就是在实际编译之前就处理了(也是编译器处理的)
main函数的返回值拥有特殊情况,开发者不写返回语句的话,它也会自动返回0,这仅限于main函数
std::cout后面的<<符号实际上是个函数(等同于.print())
win32等于X86平台(不同名字但是指的同一个东西)
每一个cpp源文件编译为一个目标文件,链接器将有关联的目标文件链接在一起
 

C++编译器是如何工作的17:56

编译器有预处理和编译功能,如果用visual studio的话,可以在设置编译器只输出.i的预处理文件或者生成编译后的.ASM或者.obj文件
.i预处理文件可以用文本编辑器打开(就像txt文本文件一样),可以看见编译器的预处理阶段将#符号后面内容都做了相应的处理:
#include <???>,就是把???文件代码粘贴复制到#处的位置。
#define ??? xxx,就是将???内容替换为xxx内容
 
.ASM 编译到汇编代码阶段的文件
.obj 编译到二进制机器码阶段的文件
 

C++链接器是如何工作的15:53

static关键字(在类和结构体外部)可以限定某函数或变量只用于(可见于)当前cpp文件,这样链接器就不会去其他文件中找该函数或变量的定义。
visual studio中output窗口中出现的error提醒中:
例如C2164等C开始是编译阶段的错误。
例如LNK的错误则是链接阶段的错误
 
inline关键词 使用在函数前面(类似static的位置,位于类型之前)将函数体替换到函数调用位置
 

C++变量13:46

各种原始变量数据类型的区别就是所占内存的大小
int数据类型的意思是在一定范围内存储整数。
int通常是4字节,但是会由编译器决定具体是多少,所以不能说一定是4字节
int大致范围是-21亿到+21亿(int占32位,其中有一位表示正负符号,剩下31位的取值范围为-2147483648~2147483647
 
unsigned 表示无符号位的类型,所以unsigned int 就是32位全部表示正数(不要符号位),取值范围0~4294967295,所以是0到42亿
整数:
基本数据类型(部分)
占用大小(字节)
char
1
short
2
int
4
long
4(通常)
long long
8
等等。。。
等等。。。
unsigned 可以和上述基本类型进行组合
char类型专用来表示字符,当然也可以直接给它赋予数值,在使用cout输出char类型变量时,cout会默认输出为字符(数值所对应的ascii表中的字符)
 
浮点数(带小数的数)
类型名
类型
所占大小(字节)
注意(不区分大小写)
浮点数
float
4
赋值数值后要加“f”符号,例如5.5f
双精度数
double
8
不加“f”默认为double
 
布尔逻辑(判断数)
类型
所占大小(字节)
注意
bool
1
赋值true(假、1) false(假、0)
bool类型实际数据只占用1bit大小,但是为什么还是要占用1字节(byte)呢,因为现在计算机寻址的过程中不能只寻1bit的数,而是1byte开始寻。
 
sizeof():用来查看某数据的所占字节大小,例如sizeof(int)或者sizeof(某个变量)
 
💡
所以:变量的类型大小与它能存储多大的数字(所占字节大小)直接相关
 

C++函数09:51

这里说的函数,并不同时指c++的class类中的方法
建议将重复使用的代码块封装成函数使用
建议函数的声明和定义分开,声明放在.h头文件里,定义放在.cpp文件中。
 

C++头文件15:10

头文件中的预处理语句#pragma once,表示在一个cpp中多次include这个头文件时,只识别一次(否则会报重复的错误)
#pragma once等价于#ifndef _??_H #define _??_H #endif(头文件监督)
比如Log.h
#pragma once#ifndef头文件监督的区分,
#pragma once比较简洁、新,且大部分编译器都支持
#ifndef是比较旧的存在,一些历史老代码会存在。
 
#include <???>#include “???.h”的区别:
< >表示去搜索包含路径文件夹,只用于编译器包含的路径
“ ”表示从当前文件的目录下开始,你可以使用引号,来指定编译器包含目录的相对路径里面的文件(上级目录的话,可以使用#include ”../???.h”
在C++中,标准库是不加.h后缀的(C++开发者定的,为的是和C语言的标准库做区分),C++标准库一般包含在编译器目录中,例如MSVC中的include目录里(例如我的位置D:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\include\iostream
在C语言中的标准库引用有.h
 

C++指针16:59

指针(void* ptr)只是一个内存地址,是个整数,类型与其本质其实没有实质性关系,指针只是个内存地址,给它定义类型只是为了操作它。
指针类型和反向引用”*“取对应地址中的实际数值有关(如果要赋值的话)
二级指针(双指针:void** ptr):就是指向指针的指针,这个指针所指的内容还是地址,去内存找这个所存地址的位置,才存的是实际数值。
 

C++引用10:13

引用(例如:int& ref = a)相当于引用地址,但是ref并不实际存在(只是在源代码中),
声明引用比如马上赋值(引用不是一个真正的变量)
 

C++字符串19:26

被称为字符串的东西都是文本字符串(文本字符串成一串)
用来表示文本,还有对应的方法可以来处理文本
字符:字母、符号、数字等都是字符,详细见ASCII表:ASCII 表 | 菜鸟教程 (runoob.com),甚至汉字也是字符,但是不在ASCII表的范围(因为ASCII码是8位表示的256个字符,但是汉字远超256的个数,所以不能用ASCII码来表示)。
数据类型char用来表示字符串(character的缩写),这是个char型指针,表现为装字符的数组,即实际上为字符数组
例如: "Cherno"的双引号表示字符串,'C'的单引号表示单个字符
对于指针来说const放在*号之前,就不能修改指针指向的内容了(const char*就是这个例子
 
错误定义字符串:这是未定义行为,需要加const才行(如上)
char* name0 = "Cherno";(注意这是错误的,不能这样写)
 
正确定义字符串:name0和name1会存在常量区,定义后就不可修改了 ,
const char* name0 = "Cherno";(常用于C语言中定义字符串)
std::string name1 = "Cherno";(是C++语言中定义字符串)
 
ASCII可以扩展为UTF-8、UTF-16、UTF-32
const char* name3 = u8"Cherno"; //utf-8,前缀写u8,实际它就是常规的const char* const wchar_t* name4 = L"Cherno";//宽字符,比char宽,字节宽度根据平台编译器变化 const char16_t* name5 = u"Cherno"; //utf-16,前缀写u,固定2字节 const char32_t* name6 = U"Cherno";//utf-32,前缀写U,固定4字节

C++类08:42

类只是对数据和功能组合在一起的一种方法
clas是类型,一种新的数据类型,结尾要加分号,比如class Player { //类的内容 };
类的默认成员是私有的,要手动设置可见性,比如public:
 

C++类与结构体对比08:32

技术上来说,类与结构体的区别在于可见性,类的可见性需要设置,默认为私有,而结构体是公共public的
以及类的继承是更抽象的存在,结构体则不适合
 
什么时候选择类或者结构体?
类:用来表示大量变量和方法功能的集合,以及需要继承的功能
结构体:用来表示一些数据的集合
 

C++枚举07:45

这里只讨论枚举,而不是枚举类
如果有一个需要由一些数字表示的数值集合,那么用枚举可以让代码更干净整洁
在这个例子中,Day 是枚举类型的名称,而 Sunday, Monday, 等是枚举的具体取值。
默认情况下,枚举的第一个取值的值是0,然后依次递增。
你也可以为枚举值指定具体的数值:
在这个例子中,Sunday 的值是 0,Monday 的值是 1,以此类推。枚举值可以使用在条件语句、switch语句等处,使代码更易读,例如
这样,通过使用枚举,代码更加清晰,你可以在不使用魔法数字的情况下表示特定的状态或选项。
 
在C++中,枚举类型的底层数据类型是整数(通常是int),但你可以显式地指定枚举的底层数据类型,以满足特定需求。在C++11及之后的版本中,可以使用enum class(也称为scoped enumeration)来定义具有指定底层数据类型的枚举。
1. 使用enum class(C++11及以上版本):
在这里,EnumName 是枚举类型的名称,underlying_type 是你希望指定的底层数据类型,可以是intcharshort等整数类型。
例如,如果你想要一个8位的枚举类型:
在这个例子中,MyEnum 是枚举类型的名称,uint8_t 是底层数据类型,表示这个枚举类型将使用8位的无符号整数来存储值。
 

C++中的静态(static)06:29

在类和结构体外部
static关键字可以限定某函数或变量只用于(可见于)当前cpp文件,这样链接器就不会去其他文件中找该函数或变量的定义。
 

C++类和结构体中的静态(static)09:12

在类和结构体内部
被static声明的变量:表示这个类或者结构体的多个实例中共享这个对象,也就是在多个实例中,这个变量只有一个实例,如果某个类的实例修改了它,那么其他所有该类的实例中的这个变量值都将改变。这下类中的任何东西都可以访问它了
被static声明的方法:该方法实际上没有类实例(static化后缺少了实例的参数),所以静态方法无法访问非静态的成员
 

C++中的局部静态(Local Static)07:40

局部静态变量允许我们声明一个变量,其生存期是整个程序的生存期,作用域局限在声明位置的局部
 

C++虚函数06:46

虚函数允许我们在子类中重写方法
virtual 声明虚函数,override 覆写函数标记
构造函数的成员初始化列表:Player(const std::string& name):m_Name(name){}
在C++中,构造函数的成员初始化列表指的是一种便捷的语法形式,它允许你在创建对象时不必显式地调用构造函数,而是直接为对象提供初始化参数。这种便捷的语法形式被称为初始化列表(constructor initializer list),它允许在对象创建时直接初始化成员变量,而不需要在构造函数体内部进行赋值操作。
成员初始化列表形式如下:
在这个例子中,MyClass类有一个私有成员变量m_Value。构造函数的语法糖 : m_Value(value) 表示在构造函数创建对象时,将传入的value参数的值直接赋值给m_Value成员变量,而不是在构造函数体内部使用赋值操作。
这种语法糖使得构造函数的实现更加简洁,同时也提供了更高效的初始化方式,避免了不必要的构造和赋值操作。这种初始化方式也在现代C++中被广泛使用。
虚函数引入了一种动态联编的东西,它通过V表(虚函数表)来实现编译,V表包含了所有基类的虚函数。
虚函数有两种性能开销(运行成本)(开销并不太大):
1.额外的内存存储V表(),包括基类的一个成员指针指向V表,
2.需要遍历V表,以用来确定要映射到哪个函数
 
 
 

C++接口(纯虚函数)06:55

纯虚函数:基类定义的接口(接口类),但是其方法并未实现,需要(强制)子类来实现,这样子类才能实例化。
virtual std::string GetName() = 0 ;
 

C++中的CONST12:54

对于一般变量来说:const修饰的变量等于不可修改的常量
对于指针来说:有两种情况
情况一:const放在*号之前const int* ptr(能修改本身,但是不能修改内容)
不能修改该指针指向的内容,也就是不能通过逆向引用*ptr来重新赋值。
情况二:const放在*号之后int* const ptr(不能修改本身,但是能修改内容)
与情况一相反,能修改指针指向的内容,但是不能修改指针本身。
 
对于类中的方法来说:const位于方法后面,承诺这个方法不会修改成员,是只读属性,但是如果有特殊的成员变量需要变化,那就可以用mutable关键词修饰这个特殊的变量就可以使用了。
 
 

C++的箭头操作符07:54

实际上是类的实例对象指针的逆向引用的快捷方式
一个实例化后的对象一般都是用“.”来调用成员对象和方法,
但是如果这个对象是被指针指向的,那么使用这个指针去调用方法的话,可用两种方法:
方法一:就需要通过逆向引用“*”来使用(得用圆括号调整优先级,例如(*ptr).GetName()
方法二:或者用“→”箭头的快捷方式例如(ptr->GetName()),直接用指向对象的指针去调用方法。
 
 

C++的三元操作符08:02

三元操作符实际上是if用法的语法糖
三元操作符格式: 条件 ? 真的执行 : 假的执行
 

创建并初始化C++对象13:03

两种创建方式
在栈上创建:
 
在堆上创建:使用new关键字(其实new是个操作符),结束后必须用delete手动释放
注意:(new数组的话new int[50] ,则需要delete[] )。
 

C++隐式转换与explicit关键字07:54

对应前面实例化对象的隐式创建,给类的构造函数使用explicit关键字可以禁止该对象的隐式创建。
explicit的意思就是明显;显式的意思。
 

C++的堆与栈内存的比较19:31

栈:在栈内创建变量的时候,变量的地址是连续的,且因为是栈,特性就是从高地址到低地址进行分配,一般数组或类与结构体之中的数据都是连续的内存地址,但是不相关的两个独立变量先后定义的话,两个地址中间是有一段独特地址装有别的数据,这好像是安全守卫?,用来防止越界访问?
 
堆:在堆内创建变量的时候,除了需要连续的变量数据(数组、结构体、类等等)是连续地址的话,每个独立的变量就算是先后定义,数据存放在堆的非连续的地址中的。
new关键字就是在堆上分配地址,它会调用malloc函数通过操作系统去获得堆资源。首先要查询空闲内存表,然后找到一段可以符合需求的容量,返回地址给变量。期间还要做大量的各种记录工作,工作流程比栈麻烦的多。
系统性能开销:栈比堆快很多,效率速度都是栈优于堆,除非需要控制实例化对象的生命周期或者需要大量的对象(超过栈的容量),那么还是优先考虑使用栈分配方式。
 

C++运算符及其重载12:44

c++的运算符(操作符)就是函数,使用一个符号来代替函数的功能(操作符的前后数据一般就是对应的函数参数)。(但是尽量少用)
逆向引用符(*)、取地址符号(&)、箭头符()、左移运算符(<<)等等。。。
甚至newdelete也是运算符。
重载:给某个东西赋予新的含义
运算符重载:给运算符赋予新的含义(添加参数、更改运算符操作等)
基本格式:类型 operator+(参数1、参数2){}
 

C++的this关键字06:09

在方法内部this是一个指向当前对象实例的指针
需要非静态的方法,可以实例化的有效对象
因为this是个指针,所以用的是()运算符(指针与的关系前面有讲解C++的箭头操作符07:54
 
 

C++的对象生存期(栈作用域生存期)11:00

{}这种大括号就是表示作用域
 

C++的模板17:59

模板,好像很高深的样子,实际上确实有点高深,所以这里先提基础使用的一部分。
模板,编译器为你写代码,基于你给它的规则,基于函数或类的使用,或类似的东西
(但是如果使用模板过度变得复杂,那么将挺噩梦的,过于复杂的模板将会难以掌控,这可能太魔法了)
 
 

C++中使用库(静态链接)18:43

静态链接:以visual studio为例,静态链接需要引入外部库文件和其头文件到项目中(解决方案中)
(静态链接发生在编译阶段,静态库会进入可执行文件;而动态链接在链接阶段,在启动可执行文件后才进入程序运行)
使用静态链接需要如下设置:给IDE添加头文件和库文件的路径设置。
头文件设置:在点击项目右键的属性——设置C/C++的常规选项中设置头文件包含(include)路径,可以使用$(SolutionDir)加相对路径设置
notion image
库文件设置:在点击项目右键的属性——设置链接器输入选项中设置附加依赖项,其中添加自己的静态库.lib的位置($(SolutionDir)加相对文件路径设置)。也可以在链接器的常规选项中设置附加库目录的绝对地址,再去输入项设置附加依赖项的相对文件路径(直接给文件名,例如,xxx.lib;)
notion image
 
头文件提供声明,告诉我们什么函数可以用,然后库提供定义,这样链接阶段就可以链接到正确的函数进行执行。
 

C++中使用动态库10:07

同静态链接相似的设置,头文件设置如上,库文件要从xxx.lib换成xxxdll.lib的库,然后把xxxdll.lib文件同路径中的xxx.dll文件复制到生成的exe可执行文件同路径中,就可以了。
只有可执行程序运行时,动态库才会加载(运行时动态的链接)
 

C++中创建与使用库12:27

创建与使用静态库,一个解决方案中,创建两个项目,一个主要项目(做主程序),一个依赖项目(做静态库),
主程序项目的属性——常规——配置类型设置为应用程序
notion image
依赖项目的属性——常规——配置类型设置为静态库
notion image
 
然后对项目进行build
 
然后为主程序项目——添加——引用——添加依赖项目
给主程序中添加依赖项目的头文件(添加方法如前面的头文件设置
将依赖项目以lib静态库形式编译到主程序中。
 

C++的auto关键字17:04

在C++中,所有的变量都要有类型,不管是整数、浮点数、字符串、结构体、类等等以及它们的指针,都需要类型,但是不一定都得程序员手动写清楚,我们可以使用auto类型,让C++编译器自己推断出本应的类型。不管是在创建、初始化或者赋值变量得时候都可以。
比如:int a = 5; auto b = a;//可以正常推导出b的类型是int,这里的auto就是int的含义了。
 
 

C++的函数指针12:41

函数指针,是将一个函数赋值给一个变量的方法(指向函数地址的指针)
void(*func)(int);
给变量赋值函数地址时,不需要对函数名前使用(&)取地址符号,因为有个隐式转换。
假如Print是个函数,
那么可以auto function = Print; function();
 

C++的lambda11:54

lambda是我们不需要通过函数定义,就可以定义一个函数的方法
lambda本质上是我们定义的一种叫做匿名函数的方式,不需要实际创建一个函数,只需要一个一次性函数的快速展示,而不像正式函数那样在实际编译的代码中作为一个符号存在。
只要你有一个函数指针,你都可以在C++中使用lambda,这就是它工作原理

C++的名称空间13:17

名称空间的主要目的是避免命名冲突,能够在不同的上下文中调用相同的符号。
使用命名空间包裹函数namespace name{},再使用:: 名称空间操作符选择空间使用,同样适合用于静态函数或者类中的符号。
::是名称空间操作符,比如std::就很常见,是标准库的名称空间
 
 
 

为什么我不使用using namespace std14:35

我们可以在特定作用域中使用它,但是不宜在全局使用它(我初学时老师就直接让我们在全局这样用,我曾一度不能理解这个语句,原来只是命名空间的作用范围)
不合适的使用using namespace std;,可能会让人难以区分函数的命名空间(如果自己别的命名空间中有和标准库函数名称一样的函数,那将容易混淆,不便区分开来。)适当勤劳的使用std::来选择命名空间的函数,有助于区分标准库和其他库。
 
XavierSu
XavierSu
一个追求精神与技术的魔怔人
公告
type
status
date
slug
summary
tags
category
icon
password
🎉NotionNext 4.0即将到来🎉
-- 感谢您的支持 ---
👏欢迎更新体验👏