Back to Top Video Links
VIDEO
Notebook Links
01. Welcome to C++ C++ for hardware, C++ for game engines
02-04. Setup C++ in different OS 05. How C++ works preprocessor statement head file main function: entry point
source file(Main.cpp) –compiler–> object files(Main.obj) –linker–> executable file(Main.exe) (Windows platform)
source file(file1.cpp, file2.cpp) –compiler–> object files(file1.obj, file2.obj) –linker–> executable file(file.exe) (Windows platform)
declaration and defination
06. How the C++ Compiler Works text form to an actual executable binary
compiling(cpp->obj)
linking(obj->exe)
translation unit
preprocessor How ‘#include ‘ works
All the compiler did was open the header file and copy whatever was in here. Let’s see a simple example.
1 2 3 4 5 6 int math (int a, int b) { int result = a * b; retrun result;#include "EndBrace.h"
1 2 3 4 5 6 #include <xx > #include "xx" #define A B #if #endif
what’s actually inside the obj file function signature
the complier’s work: It takes the source files and output an object file which contains machine code and any other constant data that we’ve defined.
07. How the C++ Linker Works 1 2 3 4 5 6 static int math (int a, int b) { int result = a * b; retrun result; }
08. Variables in C++
int -> 4 Bytes(4×8=32 bits) -> (-$2^31$)~($2^31-1$) unsigned int 4 Bytes(4×8=32 bits) -> $2^32$ char short long long long float -> float var = 5.5f; double -> double var = 5.5; bool -> 1 Byte (Although it only need 1 bit, 但在从内容中获取bool类型的数据是时候,我们没法寻址到每个bit,这能寻址到每个Byte). 0 means false
and any other digits mean true
. 为了节约这个内存空间,我们可以把8个bool类型的变量放在1个Byte的内存中,但这个是高级的操作了。
sizeof()
pointer: int* a;
reference: int& a;
09. Functions in C++ function and method
return value
.cpp
and .h
所谓的头文件,其实它的内容跟 .cpp 文件中的内容是一样的,都是 C++ 的源代码。但头文件不用被编译。我们把所有的函数声明全部放进一个头文件中,当某一个 .cpp 源文件需要它们时,它们就可以通过一个宏命令 “#include” 包含进这个 .cpp 文件中,从而把它们的内容合并到 .cpp 文件中去。当 .cpp 文件被编译时,这些被包含进去的 .h 文件的作用便发挥了。
理解 C++ 中的头文件和源文件的作用 | 菜鸟教程 (runoob.com)
1 2 3 4 #pragma once 任何一个以‘#’开始的语句都被称作预处理语句,‘pragma’是一个被输入到编译器或者是预处理器的指令,‘pragma once’意思是这说只include这个文件一次。 ‘pragma once’被称为为‘header guard’(头文件保护符),其作用是防止我们把单个头文件多次include到一个单一translation unit里。
下面这两种写法等价,都是起到了头文件保护作用。前者是现在常用是格式,后者是之前的代码常用的格式
1 2 3 4 5 6 7 #pragma once void InitLog () ;void Log (const char * message) ;struct Player {};
1 2 3 4 5 6 7 8 #ifdef _LOG_H_ #define _LOG_H_ void InitLog () ;void Log (const char * message) ;struct Player {};#endif
#include & #include “xx.h” <>只用于编译器的include路径,而””可以用于所有。
iostream这个东西看起来不想是文件呀? iostream是一个文件,只不过没有拓展名,是写标准库的人决定这么去干的,为了区分C的标准库和C++的标准库。C标准库的头文件中一般都有‘.h’的拓展名,而C++的没有。
11. How to DEBUG C++ in VISUAL STUDIO Breakpoints & Reading memory
12. CONDITIONS and BRANCHES in C++(if statements) if and else
else if其实并不是一个关键词,而是else和if的一个组合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if (ptr) Log (ptr);else if (ptr = "Hello" ) Log ("Ptr is Hello" );if (ptr) Log (ptr);else { if (ptr = "Hello" ) Log ("Ptr is Hello" ); }
13. BEST Visual Studio Setup for C++ Projects!
Visual Studio项目中的文件夹是虚拟文件夹,起到一种筛选器的作用。
bin
means binary
Output Directory: $(SolutionDir)bin$(Platform)$(Configuration)\
Intermediate Directory: $(SolutionDir)bin\intermediate$(Platform)$(Configuration)\
什么是 Visual Studio 解决方案和项目? - Visual Studio (Windows) | Microsoft Learn
14. Loops in C++ (for loops, while loops) for loops 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 for (int i = 0 ; i < 5 ; i++) { cout << "hello" << endl; } int i = 0 ; for (; i < 5 ; ) { cout << "hello" << endl; i++; }
while loops 1 2 3 4 5 6 int i = 0 ;while (i < 5 ) { cout << "hello" << endl; i++; }
for loops和while loops怎么选择,这两个基本上一样,选择哪个,主要取决于是否需要新变量(当然也无所谓)。for loops中for (int i = 0; i < 5; i++)
i是临时变量,跳出循环后i就没有定义了,而在while loops中,i是在循环体之外定义的,所有跳出while loops时,i依然有定义,其值是跳出while loops时i的数值。
do while loops 至少会执行循环一次。
1 2 3 4 5 6 int i = 0 ;do { cout << "hello" << endl; i++; } while (i < 5 );
15. Control Flow in C++ (break, continue, return) continue: loops
1 2 3 4 5 6 7 8 9 10 11 for (int i = 0 ; i < 5 ; i++) { if (i % 2 != 0 ) continue ; cout << "i = " << i << endl; }
break: loops and switch
1 2 3 4 5 6 7 8 9 10 for (int i = 0 ; i < 5 ; i++) { if (i % 2 != 0 ) break ; cout << "i = " << i << endl; }
return: get out of the function entirely
16. Pointer in C++ ⭐
raw pointer(原始指针) ✔
smart pointer(智能指针)
Computer deal with memory. Memory is everything to a computer.
指针用于管理和操控内存。
A pointer is an integer, a number which stores a memory address . That is all that is!
1 2 3 void * ptr = 0 ; void * ptr = NULL ; void * ptr = nullptr ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int var1 = 5 ; int * ptr1 = &var1; cout << "the memory address of var1 is " << ptr1 << endl; *ptr1 = 10 ; cout << "the value stored in memory address of var1 is " << *ptr1 << endl; int var2 = 6 ; int * ptr2; ptr2 = &var2; *ptr2 = 12 ; cout << "the memory address of var2 is " << ptr2 << endl; cout << "the value stored in memory address of var2 is " << *ptr2 << endl;
首先说明,在C++中,内存分为5个区:堆、占、自由存储区、全局/静态存储区、常量存储区
栈 :是由编译器在需要时自动分配,不需要时自动清除的变量存储区。通常存放局部变量、函数参数等。
堆 :是由new分配的内存块,由程序员释放(编译器不管),一般一个new与一个delete对应,一个new[]与一个delete[]对应。如果程序员没有释放掉,资源将由操作系统在程序结束后自动回收。
自由存储区 :是由malloc等分配的内存块,和堆十分相似,用free来释放。
全局/静态存储区 :全局变量和静态变量被分配到同一块内存中(在C语言中,全局变量又分为初始化的和未初始化的,C++中没有这一区分)。
常量存储区 :这是一块特殊存储区,里边存放常量,不允许修改。 (注意:堆和自由存储区其实不过是同一块区域(这句话是有问题的,下文解释),new底层实现代码中调用了malloc,new可以看成是malloc智能化的高级版本,详情参见new和malloc的区别及实现方法, 以及这一篇)
C++中堆(heap)和栈(stack)的区别(面试中被问到的题目)_c++堆和栈的区别-CSDN博客
1 2 3 4 char * buffer = new char [8 ]; memset (buffer, 0 , 8 ); delete [] buffer;
double pointer:双重指针(指针变量的指针,用一个指针变量b存储一个指针变量a的地址)
17. Reference in C++
在计算机如歌处理这两种关键字的角度看,指针和引用基本上是一回事。 引用是基于指针的一种(syntax sugar),来使得代码更易读更好学。
语法糖就相当于汉语里的成语。即,用更简练的言语表达较复杂的含义。在得到广泛接受的情况之下,可以提升交流的效率。
之所以叫【语法糖】,不只是因为加糖后的代码功能与加糖前保持一致,更重要的是,糖在不改变其所在位置的语法结构的前提下,实现了运行时等价。可以简单理解为,加糖后的代码编译后跟加糖前一毛一样。 之所以叫【语法糖】,是因为加糖后的代码写起来很爽,包括但不限于:代码更简洁流畅,代码更语义自然. 写得爽,看着爽,就像吃了糖。效率高,错误少,老公回家早… PS: 据说还有一种叫做【语法盐】的东西,主要目的是通过反人类的语法,让你更痛苦的写代码其实它同样能达到避免代码书写错误的效果,但编程效率应该是降低了,毕竟提高了语法学习门槛,让人咸到忧伤…
什么是语法糖? - 知乎 (zhihu.com)
引用是指对某个已存在 的变量的引用。
1 2 3 4 5 int a = 5 ; int * b = &a; int & ref = a;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int a = 5 ; int & ref = a; int a = 5 ; int & ref = a; ref = 10 ; cout << "a = " << a << endl; cout << "ref = " << ref << endl;
举个例子!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> using namespace std;void IncreaseValue (int value) { value++; }int main () { int a = 5 ; IncreaseValue (a); cout << "a = " << a << endl; cin.get (); }
要想使用函数把实参a的值进行改变,可以使用指针的方式来实现!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> using namespace std;void IncreaseValue (int * value) { (*value)++; }int main () { int a = 5 ; IncreaseValue (&a); cout << "a = " << a << endl; cin.get (); }
使用指针 可以改变实参a的值,但是使用引用 能更方便的实现此功能!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> using namespace std;void IncreaseValue (int & value) { value++; }int main () { int a = 5 ; IncreaseValue (a); cout << "a = " << a << endl; cin.get (); }
注意:一旦声明了一个引用,就不能更改它所引用的对象了!
1 2 3 4 5 6 7 8 9 10 11 int a = 5 ; int b = 10 ; int & ref = a; ref = b; cout << "a = " << a << endl;
另外,因为ref并不是一个实际的变量,声明ref的时候必须立刻将其作为一个真正变量的引用!
1 2 3 4 5 6 7 8 int a = 5 ; int & ref; ref = a;
18. Classes in C++⭐ Object-Oriented Programming(OOP)
Class and Object(类与对象)
C++支持:面向过程、面向对象、基于对象、泛型编程四种类型的编程;
C不支持米那些对象编程;
JAVA, C#只适合面向对象编程(不是不可以其他风格,只是最好编写面向对象编程风格的程序)
类是一种将数据和函数组织在一起的方式。
在面对很多很多变量的时候,使用class能使得代码更简洁和方便维护。
由类类型定义的变量叫做对象(object),创建新对象的过程叫做实例化(instance)。
visibility(访问控制)
默认情况下,类中的成员的访问控制都是私有的,意味着只有类内部的函数才能方位这些变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> using namespace std;class Player {public : int x, y; int speed; };void Move (Player& player, int xa, int ya) { player.x += xa * player.speed; player.y += ya * player.speed; }int main () { Player player1; player1.x = 0 ; player1.y = 0 ; player1.speed = 10 ; Move (player1, 1 , -1 ); cout << " x = " << player1.x << endl; cout << " y = " << player1.y << endl; cin.get (); }
为了使得代码更简洁,可以把函数写到类内,作为方法。这样可以使得当我们为特定的类调用Move函数的时候就是调用他自己的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> using namespace std;class Player {public : int x, y; int speed; void Move (int xa, int ya) { x += xa * speed; y += ya * speed; } };int main () { Player player1; player1.x = 0 ; player1.y = 0 ; player1.speed = 10 ; player1.Move (1 , -1 ); cout << " x = " << player1.x << endl; cout << " y = " << player1.y << endl; cin.get (); }
类也是一种语法糖。
19. Classes vs Struct in C++ C++中Class和Struct有什么区别?
基本上没什么区别😅
使用 class 时,类中的成员默认都是 private
属性的,而使用 struct 时,结构体中的成员默认都是 public
属性的.
C++中struct存在的唯一原因是因为它想要维持与C之间的兼容性,因为C中没有类但有结构体。如果把C++中的struct删除之后,C++与C存在兼容性问题。
C++中class与struct的使用,主要还是有个人编程风格决定吧。
在讨论Plain Old Data(POD)时候,使用struct; 在讨论比较复杂功能的时候,使用class;
在使用继承的时候,使用class;
在 C 语言 中,**结构体 ** 只能存放一些 变量 的集合,并不能有 **函数 **,但 C++ 中的结构体对 C 语言中的结构体做了扩充,可以有函数,因此 C++ 中的结构体跟 C++ 中的类很类似。C++ 中的 struct 可以包含成员函数,也能继承,也可以实现多态。
但在 C++ 中,使用 class 时,类中的成员默认都是 private 属性的,而使用 struct 时,结构体中的成员默认都是 public 属性的。class 继承默认是 private 继承,而 struct 继承默认是 public 继承。
C++ 中的 class 可以使用模板,而 struct 不能使用模板。
C++ class和struct区别-C++类与结构体区别-嗨客网 (haicoder.net)
POD 是 Plain Old Data 的缩写,是 C++ 定义的一类数据结构概念,比如 int、float 等都是 POD 类型的。Plain 代表它是一个普通类型,Old 代表它是旧的,与几十年前的 C 语言兼容,那么就意味着可以使用 memcpy() 这种最原始的函数进行操作。两个系统进行交换数据,如果没有办法对数据进行语义检查和解释,那就只能以非常底层的数据形式进行交互,而拥有 POD 特征的类或者结构体通过二进制拷贝后依然能保持数据结构不变。也就是说,能用 C 的 memcpy() 等函数进行操作的类、结构体就是 POD 类型的数据 。
什么是 POD 数据类型? - 知乎 (zhihu.com)
20. How to write a C++ Class Log Class: error, warning and message or trace.
插一个VS使用小技巧,如何让VS和VSCode一样有代码预览窗口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <iostream> using namespace std;class Log {public : const int LogLevelError = 0 ; const int LogLevelWarning = 1 ; const int LogLevelInfo = 2 ;private : int m_LogLevel = LogLevelInfo;public : void SetLevel (int level) { m_LogLevel = level; } void Error (const char * message) { if (m_LogLevel >= LogLevelError) cout << "[ERROR]:" << message << endl; } void Warn (const char * message) { if (m_LogLevel >= LogLevelWarning) cout << "[WARNING]:" << message << endl; } void Info (const char * message) { if (m_LogLevel >= LogLevelInfo) cout << "[INFO]:" << message << endl; } };int main () { Log log; log.SetLevel (log.LogLevelError); log.Error ("Hello error!" ); log.Warn ("Hello warning!" ); log.Info ("Hello info!" ); cin.get (); }
21. Static in C++ Static这部分从21~23
static 是 C/C++ 中很常用的修饰符,它被用来控制变量的存储方式和可见性。
static 关键字用来解决全局变量的访问范围问题
(1)在修饰变量的时候,static 修饰的静态局部变量只执行初始化一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。
(2)static 修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是 extern 外部声明也不可以。
(3)static 修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。static 修饰的变量存放在全局数据区的静态变量区,包括全局静态变量和局部静态变量,都在全局数据区分配内存。初始化的时候自动初始化为 0。
(4)不想被释放的时候,可以使用static修饰。比如修饰函数中存放在栈空间的数组。如果不想让这个数组在函数调用结束释放可以使用 static 修饰。
(5)考虑到数据安全性(当程序想要使用全局变量的时候应该先考虑使用 static)。
C/C++ 中 static 的用法全局变量与局部变量 | 菜鸟教程 (runoob.com)
22. Static for Classes and Struct in C++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> using namespace std;struct Entity { int num; void Print () { cout << num << endl; } };int main () { Entity e1; e1.num = 2 ; Entity e2; e2.num = 5 ; cout << e1.num << endl; cout << e2.num << endl; cout << &(e1.num) << endl; cout << &(e2.num) << endl; cin.get (); }
上面这段代码比较容易理解,e1和e1是结构体Entity的两个不同的实例,不同实例中的num是不同的变量,我们从两个变量的地址也可以看得出来。
如果把结构体Entity的变量变为static类型的话,情况又有什么不一样呢?
1 2 3 4 5 09:36:12:204 1>Main.obj : error LNK2001: unresolved external symbol "public: static int Entity::num" (?num@Entity@@2HA) 09:36:12:250 1>E:\userDoc\ChernoDevCPP\NewProject\bin\Win32\Debug\NewProject.exe : fatal error LNK1120: 1 unresolved externals
要解决这个问题,我们必须在代码中定义这些静态变量,像这样,
现在代码能运行了,看一下结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> using namespace std;struct Entity { static int num; void Print () { cout << num << endl; } };int Entity::num; int main () { Entity e1; e1.num = 2 ; Entity e2; e2.num = 5 ; cout << e1.num << endl; cout << e2.num << endl; cout << &(e1.num) << endl; cout << &(e2.num) << endl; cin.get (); }
所以,e1.num
和e2.num
本质上都是同一个变量,所以这样的写法是没有意义的。可以写成如下的形式,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> using namespace std;struct Entity { static int num; void Print () { cout << num << endl; } };int Entity::num;int main () { Entity e1; Entity::num = 2 ; Entity e2; Entity::num = 5 ; cout << Entity::num << endl; cout << Entity::num << endl; cout << &(Entity::num) << endl; cout << &(Entity::num) << endl; cin.get (); }
23.Local Static in C++⭐
生命周期(lifetime)
作用域(scope)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> using namespace std;void Function () { int i = 0 ; i++; cout << "i = " << i << endl; }int main () { Function (); Function (); Function (); cin.get (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> using namespace std;int i = 0 ;void Function () { i++; cout << "i = " << i << endl; }int main () { Function (); Function (); Function (); cout << "main: i = " << i << endl; cin.get (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> using namespace std;void Function () { static int i = 0 ; i++; cout << "i = " << i << endl; }int main () { Function (); Function (); Function (); cin.get (); }
24. Enums in C++
枚举类型的定义:枚举类型(enumeration)是 C++ 中的一种派生数据类型,它是由用户定义的若干枚举常量的集合。
C++ 枚举类型详解 | 菜鸟教程 (runoob.com)
25. Constructors in C++ Constructors是一种特殊的method,它在实例化时被调用。
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
C++ 类构造函数 & 析构函数 | 菜鸟教程 (runoob.com)
对于一个类,在实例化之后,如果直接调用类内的变量,会用链接错误,因为类内的变量未被初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> using namespace std;class Entity {public : float X, Y; void Print () { cout << X << ", " << Y << endl; } };int main () { Entity e; cout << e.X << ", " << e.Y << endl; e.Print (); cin.get (); }
手动初始化。在类内定义一个初始化函数,把类内的变量初始化一个值,这样就不会有链接错误了。但这样不够优雅 ,在类有多个实例化时,需要每次实例化之后都使用这个初始化函数。而C++提供了更优雅有效的方式,就是构造函数(Constructors)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> using namespace std;class Entity {public : float X, Y; void Print () { cout << X << ", " << Y << endl; } void Init () { X = 0.0f ; Y = 0.0f ; } };int main () { Entity e; e.Init (); cout << e.X << ", " << e.Y << endl; e.Print (); cin.get (); }
Constructors是一种特殊的method,它在实例化时被调用以初始化实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> using namespace std;class Entity {public : float X, Y; Entity () { X = 0.0f ; Y = 0.0f ; } void Print () { cout << X << ", " << Y << endl; } };int main () { Entity e; cout << e.X << ", " << e.Y << endl; e.Print (); cin.get (); }
在C++中其实有一个默认的Constructor,但是它本身不做任何事情,方法内部是空的,就像这样
因此,C++不能自动帮我们初始化内存空间,得自己手动完成这个过程。
含参数的构造函数。3
中,类中变量的初始化数值是在类中写定的,使用含参数的构造函数能在实例化的时候确定类中变量的初始化数值,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> using namespace std;class Entity {public : float X, Y; Entity (float x, float y) { X = x; Y = y; } void Print () { cout << X << ", " << Y << endl; } };int main () { Entity e (5.0f , 6.0f ) ; cout << e.X << ", " << e.Y << endl; e.Print (); cin.get (); }
26. Destructors in C++
Constructor:构造函数
Destructors: 析构函数
构造函数通常是设置变量的地方启动或执行所需要执行的任何类型的初始化,类似的,析构函数是取消初始化任何内容的地方你可能需要删除或清楚任何已使用的内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <iostream> using namespace std;class Entity {public : float X, Y; Entity () { X = 0.0f ; Y = 0.0f ; cout << "Created Entity!" << endl; } ~Entity () { cout << "Destroyed Entity!" << endl; } void Print () { cout << X << ", " << Y << endl; } };void Function () { Entity e; e.Print (); }int main () { Function (); cin.get (); }
析构函数在类的实例化是生命周期末期被调用! 如上面的代码,“Entity e;”创造了一个实例化e,这时候调用构造函数,函数“Function()”是实例化的作用域,“Function()”函数结束的时候,调用了析构函数。 如果不使用析构函数,可能会导致内存泄漏。
27. Inheritance in C++⭐ 继承提供了一种来实现把多个类之间的公共代码转换为基类的方式,就像是一种模板。
Polymorphic(多态)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream> using namespace std;class Entity {public : float X, Y; void Move (float xa, float ya) { X += xa; Y += ya; } };class Player : public Entity {public : const char * Name; void PrintName () { cout << Name << endl; } };int main () { Player player; player.Name = "Tom" ; player.X = 0 ; player.Y = 0 ; player.Move (5 , 5 ); player.PrintName (); cin.get (); }
继承 (inheritance) 就是在一个已存在的类的基础上建立一个新的类.
已存在的类: 基类 (base class) 或父类 (father class)
新建立的类: 派生类 (derived class) 或子类 (son class)
一个新类从已有的类获得其已有特性, 称为类的继承.
通过继承, 一个新建的子类从已有的父类那里获得父类的特性
派生类继承了基类的所有数据成员和成员函数, 并可以对成员做必要的增加或调整
从已有的类 (父类) 产生一个新的子类, 称为类的派生.
类的继承是用已有的类来建立专用新类的编程技术
一个基类可以派生出多个派生类, 每一个派生类又可以作为基类再派生出新的派生类. 因此基类和派生类是相对而言的
派生类是基类的具体化, 而基类则是派生类的抽象
版权声明:本文为CSDN博主「我是小白呀」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_46274168/article/details/11659272
多态 多态polymorphism ,基本上来说就是使用一个单一的符号来表示多个不同的类型
28. Virtual functions in C++⭐ Virtual functions(虚函数)
虚函数允许我们覆盖基类中的方法。
虚函数引入了一种动态分配(Dynamic Dispatch)的东西,通常使用VTable(虚函数表)来实现编译。VTable中包含基类中所有虚函数的映射,以便我们能在运行时映射它们向正确的覆写函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <string> using namespace std;class Entity {public : string GetName () { return "Entity" ; } };class Player : public Entity {private : string m_Name;public : Player (const string& name) : m_Name (name) {} string GetName () { return m_Name; } };void PrintName (Entity* entity) { cout << entity->GetName () << endl; }int main () { Entity* e = new Entity (); PrintName (e); Player* p = new Player ("Cherno" ); PrintName (p); cin.get (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> #include <string> using namespace std;class Entity {public : virtual string GetName () { return "Entity" ; } };class Player : public Entity {private : string m_Name;public : Player (const string& name) : m_Name (name) {} string GetName () override { return m_Name; } };void PrintName (Entity* entity) { cout << entity->GetName () << endl; }int main () { Entity* e = new Entity (); PrintName (e); Player* p = new Player ("Cherno" ); PrintName (p); cin.get (); }
29. Interfaces in C++(Pure Virtual Functions)⭐ Pure Virtual Functions(纯虚函数),C++中的纯虚函数的本质上犹如Java和C#中的抽象方法和接口 。 原理上来讲,纯虚函数允许我们定义一个在基类中没有实现的函数,然后迫使在子类中实际实现,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <iostream> #include <string> using namespace std;class Printable {public : virtual string GetClassName () = 0 ; };class Entity : public Printable {public : virtual string GetName () { return "Entity" ; } string GetClassName () override { return "Entity" ; } };class Player : public Entity {private : string m_Name;public : Player (const string& name) : m_Name (name) {} string GetName () override { return m_Name; } string GetClassName () override { return "Palyer" ; } };void PrintName (Entity* entity) { cout << entity->GetName () << endl; }void Print (Printable* obj) { cout << obj->GetClassName () << endl; }int main () { Entity* e = new Entity (); Player* p = new Player ("Cherno" ); Print (e); Print (p); cin.get (); }
30. Visibility in C++ private, protected, public
私有 成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类 和友元函数 可以访问私有成员。
默认情况下,类的所有成员都是私有的。
C++ 类访问修饰符 | 菜鸟教程 (runoob.com)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <string> using namespace std;class Entity { private : int X;public : Entity () { X = 0 ; } };class Player : public Entity {public : Player () { } };int main () { Entity e; cin.get (); }
protected(受保护) 成员变量或函数与私有成员十分相似,但有一点不同,protected(受保护)成员在派生类(即子类)中是可访问的。
C++ 类访问修饰符 | 菜鸟教程 (runoob.com)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> #include <string> using namespace std;class Entity {protected : int X; void Print () {}public : Entity () { X = 0 ; Print (); } };class Player : public Entity {public : Player () { X = 10 ; Print (); } };int main () { Entity e; cin.get (); }
公有 成员在程序中类的外部是可访问的。
C++ 类访问修饰符 | 菜鸟教程 (runoob.com)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <string> using namespace std;class Entity {public : int X; void Print () {}public : Entity () { X = 0 ; Print (); } };class Player : public Entity {public : Player () { X = 10 ; Print (); } };int main () { Entity e; e.X = 10 ; e.Print (); cin.get (); }
31. Arrays in C++ Array and Pointer 1 2 3 4 5 6 7 8 9 10 11 12 int example[5 ];int * ptr = example; example[2 ] = 10 ; cout << "example[2] =" << example[2 ] << endl; *(ptr + 2 ) = 20 ; cout << "example[2] =" << example[2 ] << endl; *(int *)((char *)ptr + 8 ) = 30 ; cout << "example[2] =" << example[2 ] << endl; # 这三行代码等价!ptr+2 中的“2 ”并不是数值2 ,指针+2 的时候会自动根据数据类型来计算实际的字节数。
Stack and Heap
Stack:
和堆一样存储在计算机 RAM 中。
在栈上创建变量的时候会扩展,并且会自动回收。
相比堆而言在栈上分配要快的多。
用数据结构中的栈实现。
存储局部数据,返回地址,用做参数传递。
当用栈过多时可导致栈溢出(无穷次(大量的)的递归调用,或者大量的内存分配)。
在栈上的数据可以直接访问(不是非要使用指针访问)。
如果你在编译之前精确的知道你需要分配数据的大小并且不是太大的时候,可以使用栈。
当你程序启动时决定栈的容量上限。
Heap:
和栈一样存储在计算机RAM。
在堆上的变量必须要手动释放,不存在作用域的问题。数据可用 delete, delete[] 或者 free 来释放。
相比在栈上分配内存要慢。
通过程序按需分配。
大量的分配和释放可造成内存碎片。
在 C++ 中,在堆上创建数的据使用指针访问,用 new 或者 malloc 分配内存。
如果申请的缓冲区过大的话,可能申请失败。
在运行期间你不知道会需要多大的数据或者你需要分配大量的内存的时候,建议你使用堆。
可能造成内存泄露。
什么是堆? 什么是栈? - 知乎 (zhihu.com)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int arr1[5 ]; for (int i = 0 ; i < 5 ; i++) { arr1[i] = i; }int * arr2 = new int [5 ]; for (int i = 0 ; i < 5 ; i++) { arr2[i] = i; }delete [] arr2;
这里发现了一个新且有趣的知识点!在stack上定义的变量,自动初始化为“cccc”,而在heap上定义的变量,是自动初始化为“cdcd”,不知道是为什么会这样??
C++11 standard array
size of array
在原生数组中,计算数组的大小使用sizeof()
方法,但是这种方法也仅仅适用于定义在stack上的数组;对于定义在heap上的数组,使用sizeof()
后,返回值是指针的大小,下面的例子中,返回值是4,即整型类型的指针的大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Entity {public : int array1[5 ]; int * array2 = new int [5 ]; Entity () { int count = sizeof (array1) / sizeof (int ); cout << "count of array1 is " << count << endl; count = sizeof (array2) / sizeof (int ); cout << "count of array2 is " << count << endl; for (int i = 0 ; i < count; i++) { array1[i] = i; array2[i] = i; } } };
需要注意的是,当定义一个stack上的数组的时候,数组的大小必须是在编译时就需要注意的常量!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int array1Size = 5 ;int array1[array1Size];const int array1Size = 5 ;int array1[array1Size];static const int array1Size = 5 ;int array1[array1Size];
std::array
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include <array> using namespace std;int main () { array<int , 5> array1; for (int i = 0 ; i < array1.size (); i++) { array1[i] = i; } cin.get (); }
32. How Strings Work in C++ String, Pointer, Array, Memory address String is a group of characters
字符串结束的标志”\0“,在内存中存储的就是0。
const char* name = "Cherno";
这声明了一个指向常量字符的指针。这意味着指针name
指向的字符串内容是不可修改的。你可以通过name
指针读取字符串,但是尝试通过name
指针修改字符串的内容将导致编译错误。
1 2 3 4 5 const char * name = "Cherno" ;char firstChar = name[0 ];
char* name = "Cherno";
这声明了一个指向字符的指针,但没有使用const
。这意味着指针name
指向的字符串内容是可修改的。然而,这在 C++ 中是不安全的,因为字符串常量(像 “Cherno”)通常存储在只读的内存区域,尝试修改它们可能导致未定义的行为。
1 2 3 char * name = "Cherno" ;
总的来说,如果你知道字符串不会被修改,最好使用第一个声明,即带有const
的版本,以提高代码的安全性。如果你确实需要修改字符串,最好将字符串复制到一个可修改的内存区域,例如使用char[]
数组:
char name[] = "Cherno";
1 2 char name[] = "Cherno" ; name[0 ] = 'X' ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> int main () { const char * name = "Cherno" ; std::cout << name << std::endl; char name2[7 ] = { 'C' , 'h' , 'e' , 'r' , 'n' ,'o' , '\0' }; name2[0 ] = 'A' ; std::cout << name2 << std::endl; std::cin.get (); }
Standard string (std::string)
字符串定义与字符串函数。使用string定义的字符串变量其本质还是const char* name.
1 2 3 4 5 std::string name = "Cherno" ; cout << name << endl; std::cout << name.size () << std::endl; std::cout << name.find ("no" ) << std::endl;
字符串拼接
1 2 3 4 5 6 7 8 9 10 std::string name1 = "Cherno1" ; name1 += " hello" ; std::cout << name1 << std::endl; std::string name2 = std::string ("Cherno2" ) + " hello" ; std::cout << name2 << std::endl;
字符串作为函数参数传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <string> void PrintString (std::string onename) { onename += " hello" ; std::cout << onename << std::endl; }int main () { std::string name = "Cherno" ; PrintString (name); std::cout << name << std::endl; std::cin.get (); }
std::string onename
是对std::string name = "Cherno"
的复制,在PrintString
函数中对onename做出的修改,实际上并不会影响原来name的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <string> void PrintString (std::string& onename) { onename += " hello" ; std::cout << onename << std::endl; }int main () { std::string name = "Cherno" ; PrintString (name); std::cout << name << std::endl; std::cin.get (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <string> void PrintString (const std::string& onename) { std::cout << onename << std::endl; }int main () { std::string name = "Cherno" ; PrintString (name); std::cout << name << std::endl; std::cin.get (); }
33. String Literals in C++ 1. 字符串字面量
生成自ChatGPT
字符串字面量(String literals)是在源代码中直接表示字符串值的一种方式。在C++中,字符串字面量通常是由双引号括起来的字符序列。
例如:
1 const char * str = "Hello, World!" ;
上述代码中,"Hello, World!"
就是一个字符串字面量。这个字符串字面量的类型是一个 const char
数组(C++中字符串字面量的类型是一个字符数组),并且它以 null 字符 '\0'
结尾。
字符串字面量可以用于初始化字符数组、字符串指针、以及各种支持字符串操作的标准库类(比如 std::string
)。
在C++中,有一些特殊的字符串字面量前缀,用于指定不同的字符集和字符宽度,如前面提到的:
L
前缀表示宽字符(wchar_t
)。
u
前缀表示UTF-16字符串(char16_t
)。
U
前缀表示UTF-32字符串(char32_t
)。
示例:
1 2 3 const wchar_t * wideStr = L"Wide String" ;const char16_t * utf16Str = u"UTF-16 String" ;const char32_t * utf32Str = U"UTF-32 String" ;
字符串字面量是C++中处理字符串的基本元素之一,而且它们的使用非常普遍。在实际编程中,字符串字面量经常用于初始化字符串,作为函数参数传递,或者用于各种字符串操作。
2. 字符串长度与数组大小
There is a question! 字符串有6个字符,为什么定义中的长度是7呢?
因为要考虑到‘null termination character ’也要占用一个字符长度。
3. const char* name
Cherno在视频中提到了,这两行代码能不能编译取决于所使用的编译器。Cherno用的是VS2017可以编译,但在VS2019不能编译,VS2022中也不能编译,要求必须有const
关键字。
4. diffierent ‘char’ 1 2 3 4 5 6 7 const char * name = "Cherno" ; const wchar_t * name2 = L"Cherno" ; const char16_t * name3 = u"Cherno" ; const char32_t * name4 = U"Cherno" ;
解释来自ChatGPT
这是 C++ 中用于定义字符串的一些不同方式,涉及到字符集和字符宽度:
**const char\* name = "Cherno";
**(UTF-8)
这是一个普通的 C 字符串(null-terminated string),使用 const char*
类型的指针来指向以 null 结尾的字符序列。这是常见的字符串表示方式,其中字符的宽度是 1 字节。
const wchar_t\* name2 = L"Cherno";
这是使用宽字符集的字符串,L
前缀表示宽字符(wide character)。wchar_t
类型的指针指向以 null 结尾的宽字符序列。宽字符通常是 2 字节,用于支持更广泛的字符集,特别是国际化和多语言环境。
**const char16_t\* name3 = u"Cherno";
**(UTF-16)
这是使用 UTF-16 编码的 Unicode 字符串,u
前缀表示 UTF-16 字符。char16_t
类型的指针指向以 null 结尾的 UTF-16 字符序列。每个字符通常占据 2 字节。
**const char32_t\* name4 = U"Cherno";
**(UTF-32)
这是使用 UTF-32 编码的 Unicode 字符串,U
前缀表示 UTF-32 字符。char32_t
类型的指针指向以 null 结尾的 UTF-32 字符序列。每个字符通常占据 4 字节。
这些不同的字符串表示方式主要用于处理不同的字符集和字符宽度需求。在选择使用哪种类型的字符串时,需要考虑你的应用程序的特定要求,特别是对字符集的支持和国际化的需求。 C++11 引入了这些新的字符串类型和前缀,以更好地支持 Unicode 字符和不同的字符宽度。
虽然我们一直说 wchar 每个字符都是 2 字节,但实际上是由编译器决定的。(Windows:2 bytes,Linux:4 bytes)。 如果你希望它一直是 2 bytes,你可以用char16_t
5. 在字符串上附加一些东西
string_literals
in C++ 17
R method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> #include <string> #include <stdlib.h> int main () { std::string name1 = std::string ("Line1\n" ) + "Line2\n" + "Line3\n" ; std::cout << name1 << std::endl << std::endl; using namespace std::string_literals; std::string name2 = "Line1\n" s + "Line2\n" + "Line3\n" ; std::cout << name2 << std::endl << std::endl; const char * name3 = R"(Line1 Line2 Line3)" ; std::cout << name3 << std::endl << std::endl; const char * name4 = "Line1\n" "Line2\n" "Line3\n" ; std::cout << name4 << std::endl << std::endl; std::cin.get (); }
6. the memory of the string literals and how it works 字符串字面量总是存储在只读内存(read-only memory)中
34. CONST in C++⭐
我比较喜欢把const
叫做一个”fake keyword”,因为它实际上在生成代码的时候并没有做什么。 它有点像类和结构体的可见性,是一种针对开发人员写代码的强制规则,为了让代码保持整洁的机制。
基本上 const 就是你做出承诺,某些东西是不变的,是不会改动的。但是它只是个承诺,而且你可以绕过或不遵守这个承诺,就像在现实生活中一样。
1 const int MAX_NUMBER = 100 ;
1. const 与 pointer 当使用const处理指针的时候,可以是指针本身,也可以是指针指向的内容,取决于const放在声明处的某处,const是在“星号”的左边还是在“星号”的右边。
const
类型限定符(type qualifier)是C++语言设计的一大亮点。我们围绕着这个语言特性使用“const
正确性” (const correctness)的实践来防止const
对象遭到改变。
1 2 3 4 const int MAX_NUM = 100 ;int * a = new int ; *a = 2 ; a = &MAX_NUM;
问题在于,a
是一个指向动态分配内存的指针,而 &MAX_NUM
是一个指向常量的指针。你不能将一个指向常量的指针赋值给一个非常量指针,因为这违反了 const-correctness 规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <string> int main () { const int MAX_NUM = 100 ; int * a = new int ; *a = 2 ; a = (int *)&MAX_NUM; std::cout << *a << std::endl; std::cin.get (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <string> int main () { const int MAX_NUM = 100 ; const int * a = new int ; *a = 2 ; a = (int *)&MAX_NUM; std::cout << *a << std::endl; std::cin.get (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> #include <string> int main () { const int MAX_NUM = 100 ; int * const a = new int ; *a = 2 ; a = (int *)&MAX_NUM; std::cout << *a << std::endl; std::cin.get (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> #include <string> int main () { const int MAX_NUM = 100 ; const int * const a = new int ; *a = 2 ; a = (int *)&MAX_NUM; std::cout << *a << std::endl; std::cout << a << std::endl; std::cin.get (); }
2. 类与方法中使用const 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Entity {private : int m_X, m_Y;public : int GetX () const { return m_X; } void SetX (int x) { m_X = x; } };
1 2 3 4 5 6 7 8 9 10 class Entity {private : int * m_X, m_Y;public : const int * const GetX () const { return m_X; } };
ChatGPT
这段代码定义了一个名为 Entity
的类,其中包含了一个私有的指针成员 m_X
和一个私有的整数成员 m_Y
。以下是对这段代码的详细解释:
1 2 3 4 5 6 7 8 9 10 cppCopy codeclass Entity {private : int * m_X, m_Y;public : const int * const GetX () const { return m_X; } };
私有成员变量:
int* m_X
: 是一个指向整数的指针。这是一个指针,可以指向一个整数值。但请注意,只有指针本身是私有的,而不是指针指向的内容。
int m_Y
: 是一个整数。这个成员变量是一个普通的整数。
公有成员函数 GetX
:
const int*
: 表示返回的指针指向的内容是常量,即不能通过这个指针修改对象的值 。
const int* const
: 表示返回的指针本身也是常量,即不能通过这个指针修改指针的值 ,即不能使它指向其他内存地址。
const
关键字在函数的末尾表示这是一个常量成员函数,即在函数内不能修改对象的成员变量 。
GetX 函数的作用:
GetX
函数返回一个指向整数的常量指针,指向对象内部的 m_X
成员。
由于返回的指针是常量指针,外部代码不能通过这个指针修改 m_X
指向的内容。
同时,由于返回的指针本身也是常量指针,外部代码不能通过这个指针修改它指向的内存地址,即不能改变 m_X
的指向。
这种设计可以提供一定程度的数据封装和安全性,限制了外部对 m_X
的直接修改。
需要注意一个很有趣的地方!
1 2 3 4 - int* m_X, m_ Y; - 这样写的代表着m_X是int型指针变量,而m_ Y仍然是int型变量- int* m_X, *m_ Y - 而这样写才能使得两个变量都是int型指针变量
35. The Mutable Keyword in C++ mutable
means something can be changed.
1. mutable and const 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <string> class Entity {private : std::string m_Name; mutable int m_DebugCount = 0 ;public : const std::string& GetName () const { m_DebugCount++; return m_Name; } };int main () { const Entity e; e.GetName (); std::cin.get (); }
2. mutable and lambda 1 2 3 4 5 6 7 8 int x = 8 ;auto f = [=]() mutable { x++; std::cout << x << std::endl; }f ();
36. Member Initializer Lists in C++ (Constructor Initializer List)⭐
成员初始化列表,在构造函数中初始化类成员(变量)的一种方式
1. 构造函数->初始化成员(变量) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> #include <string> class Entity {private : std::string m_Name;public : Entity () { m_Name = "Unknow" ; } Entity (const std::string& name) { m_Name = name; } const std::string& GetName () const { return m_Name; } };int main () { const Entity e0; std::cout << e0.GetName () << std::endl; const Entity e1 ("Cherno" ) ; std::cout << e1.GetName () << std::endl; std::cin.get (); }
2. 成员初始化列表
确保成员初始化列表时,要与成员变量声明时的的顺序一致 !!
为什么需要成员初始化列表?
因为构造函数的功能往往不仅仅是初始化成员变量,为了使得构造函数看起来简洁易读一些,我们可以把杂乱的初始化成员变量的这一部分以成员初始化列表的形式单独写做一行,这样就简化了构造函数。-> 1. 简化构造函数
简化构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> #include <string> class Entity {private : std::string m_Name; int m_Score;public : Entity () : m_Name ("Unknown" ), m_Score (0 ) { } Entity (const std::string& name, const int score) : m_Name (name), m_Score (score) { m_Name = name; } const std::string& GetName () const { return m_Name; } const int & GetScore () const { return m_Score; } };int main () { const Entity e0; std::cout << e0.GetName () << ", " << e0.GetScore () << std::endl; const Entity e1 ("Cherno" , 10 ) ; std::cout << e1.GetName () << ", " << e1.GetScore () << std::endl; std::cin.get (); }
避免构造两次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <string> class Example {public : Example () { std::cout << "Created Example!" << std::endl; } Example (int x) { std::cout << "Created Example with " << x << "!" << std::endl; } };class Entity {private : std::string m_Name; Example m_Example;public : Entity () { m_Name = std::string ("Unknown" ); m_Example = Example (100 ); } };int main () { const Entity e0; std::cin.get (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> #include <string> class Example {public : Example () { std::cout << "Created Example!" << std::endl; } Example (int x) { std::cout << "Created Example with " << x << "!" << std::endl; } };class Entity {private : std::string m_Name; Example m_Example;public : Entity () : m_Name ("Unkonwn" ), m_Example (Example (100 )) { } };int main () { const Entity e0; std::cin.get (); }
37. Ternary Operator in C++(Conditional Assignment)
Ternary Operator: 三元运算符-> 问号和冒号(本质上就是if语句的语法糖)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <string> static int s_Level = 1 ;static int s_Speed = 2 ;int main () { if (s_Level > 5 ) s_Speed = 10 ; else s_Speed = 5 ; s_Speed = s_Level > 5 ? 10 : 5 ; std::string rank = s_Level > 10 ? "Master" : "Beginner" ; s_Speed = s_Level > 5 ? s_Level > 10 ? 15 : 10 : 5 ; std::cin.get (); }
38. How to create/instantiate object C++⭐
C++创建对象
实例化定义的类
1. 在栈上创建对象(stack)
几乎所有时候。如果你可以这样创建对象的话,那就这么来创建,这是基本规则。 因为在 C++中这是初始化对象最快的方式和最受管控的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <string> class Entity {private : std::string m_Name;public : Entity () : m_Name ("Unkown" ) {} Entity (const std::string name) : m_Name (name) {} const std::string& GetName () const { return m_Name; } };int main () { Entity entity; std::cout << entity.GetName () << std::endl; Entity entity1 ("Cherno" ) ; std::cout << entity1.GetName () << std::endl; std::cin.get (); }
定义在函数内的对象在函数结束之后,所占用的内存便会被释放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> #include <string> class Entity {private : std::string m_Name;public : Entity () : m_Name ("Unkown" ) {} Entity (const std::string name) : m_Name (name) {} const std::string& GetName () const { return m_Name; } };void Function () { Entity entity ("Cherno" ) ; int a = 10 ; }int main () { Function (); std::cin.get (); }
2.
1 2 3 4 5 console输入的内容如下: 1. Cherno 2. 解释一下为什么是这样的输出。
叫做Cherno的entity实例的生命周期仅在大括号之内,跳出大括号后,这个叫 Cherno 的 entity 对象已经不存在了,它已经不存在栈结构里了,所以就没有输出了。
另一个我们不想在栈上分配的原因可能是:如果这个 entity 太大了,同时我们可能有很多的 entity,我们就可能没有足够的空间来进行分配,因为栈通常都很小,一般是一两兆,这取决于你的平台和编译器。 因此你可能不得不在heap 上进行分配。
突然想到的一个内容,和本节内容相关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int main () { Entity* e; { Entity entity ("Cherno" ) ; e = &entity; std::cout << entity.GetName () << std::endl; std::cout << e->GetName () << std::endl; std::cout << (*e).GetName () << std::endl; } std::cin.get (); }
std::cout << entity.GetName() << std::endl;
直接通过对象 entity
调用 GetName
函数,输出实体的名称。
这种方式是直接访问对象的成员函数,因为 entity
是 Entity
类的一个实例。
std::cout << e->GetName() << std::endl;
通过指针 e
调用 GetName
函数,输出实体的名称。
这种方式使用了指针,e
是一个指向 Entity
对象的指针,通过箭头运算符 ->
访问对象的成员函数。
std::cout << (\*e).GetName() << std::endl;
同样是通过指针 e
调用 GetName
函数,输出实体的名称。
这种方式使用了解引用操作符 *
,先解引用指针,然后再访问对象的成员函数。
在这个特定的示例中,这三种方式都会输出相同的结果,即实体的名称。选择使用哪种方式通常取决于代码的上下文和个人偏好。在一般情况下,直接通过对象调用成员函数是最直观和常见的方式。使用指针或引用通常用于处理动态分配的对象或在函数参数中传递对象,但需要小心确保指针有效且指向有效的对象。
2. 在堆上分配(heap) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <string> class Entity {private : std::string m_Name;public : Entity () : m_Name ("Unkown" ) {} Entity (const std::string name) : m_Name (name) {} const std::string& GetName () const { return m_Name; } };int main () { Entity* e; { Entity* entity = new Entity ("Cherno" ); e = entity; std::cout << "1. " << entity->GetName () << std::endl; } std::cout << "2. " << e->GetName () << std::endl; std::cin.get (); delete e; }
Attention: 在使用了new关键字之后,不用的内存要注意使用delete关键字释放掉,防止内存泄漏。
3. 总结 两种创建对象的方法如何选择?
如果要创建的对象很大-> heap
显式地控制对象的生存期 -> heap
其他 -> stack
39. The New keyword in C++
关于连续内存的问题,计算机并不是搜索出来的这个4 bytes的连续内存,而是存在一种叫做空闲列表(free list)的东西,它会维护那些有空闲字节的地址。
new的作用就是要找到一个足够大的内存块,以满足我们的需求。
Entity* e = new Entity();
在这里它不仅分配了空间,还调用了构造函数。
通常,调用new 关键字会调用底层的C函数malloc ,它是用来分配内存的。 malloc()
的实际作用是,传入一个size
,也就是我们需要多少个字节,然后返回一个void指针
new
本身实际上是一个operator(操作符),操作符意味着可以操作符重载
用完new
之后记得使用delete
C++中的new和delete对应到C中就是malloc和free
1 2 3 4 5 6 7 8 int * a = new int ;delete a;int * b = new int [50 ];delete [] b; Entity* e = new Entity ();delete e;
placement new
int* b = new int[50];
Entity* e = new(b) Entity();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 - 这决定了它的内存来自哪里,细节以后再讲,这里只是展示它的语法。 - 这里将 `Entity` 对象构造在已分配的内存地址 `b` 上,而不是使用默认的内存分配器。这样可以在指定的内存位置创建对象。这行代码在 `b` 指针指向的内存位置上构造了一个 `Entity` 对象,并返回指向该对象的指针,并将其赋给了 `e` 指针。 # 40. Implicit Conversion and the Explicit keyword in C++ > - 隐式转换与explicit关键字 > > implicit :隐式的 > explicit:显式的 > > - *implicit *(隐式)的意思是不会明确地告诉你它要做什么,它有点像在某种情况下自动的工作。实际上 C++允许编译器对代码进行一次隐式的转换。 > > - 如果我开始使用一种数据类型作为另一种类型来使用,在这两种类型之间就会有类型转换,C++允许隐式地转换,不需要用*cast*等做强制转换。 ## 1. Implicit Conversion ```C++ #include <iostream> #include <string>class Entity {private : std::string m_Name; int m_Age;public : Entity(const std::string& name ) : m_Name(name ), m_Age(-1 ) {} Entity(int age) : m_Name("Uknown" ), m_Age(age) {} };int main() { Entity a("Cherno" ); Entity b(22 ); Entity c = Entity("Cherno" ); Entity d = Entity(22 ); Entity e = std::string("Cherno" ); // 隐式类型转换 Entity f = 22 ; // 隐式类型转换 std::cin.get(); }
2. explicit keyword
如果把explicit关键字放在构造函数之前,这就意味着不能使用隐式构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include <string> class Entity {private : std::string m_Name; int m_Age;public : Entity (const std::string& name) : m_Name (name), m_Age (-1 ) {} explicit Entity (int age) : m_Name("Uknown" ), m_Age(age) { } };int main () { Entity a ("Cherno" ) ; Entity b (22 ) ; Entity c = Entity ("Cherno" ); Entity d = Entity (22 ); Entity e = std::string ("Cherno" ); Entity f = (Entity)22 ; std::cin.get (); }
41. Operators and Operator overloading in C++⭐ 1. 运算符 1 2 3 4 5 operator: - '+', '-', '*', '/' - '*(dereference)', '->', '+=', '&', '<<', - 'new', 'delete', - ',', '()', '[]'
2. 运算符重载 + and -
overload 重载这个术语本质就是给运算符重载赋予新的含义,或者添加参数,或者创建 允许在程序中国定义或更改运算符的行为。
不过说到底,运算符就是function ,就是函数。 与其写出函数名add
,你只用写一个+
这样的运算符就行,在很多情况下这真的有助于让你的代码更干净整洁,可读性更好。
运算符重载的使用应该非常少,而且只是在完全有意义的情况下使用。
没有运算符重载的时候写的程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <string> struct Vector2 { float x, y; Vector2 (float x, float y) : x (x), y (y) {} Vector2 Add (const Vector2& other) const { return Vector2 (x + other.x, y + other.y); } Vector2 Multiply (const Vector2& other) const { return Vector2 (x * other.x, y * other.y); } };int main () { Vector2 position (4.0f , 4.0f ) ; Vector2 Speed (0.5f , 1.5f ) ; Vector2 Powerup (1.1f , 1.1f ) ; Vector2 result = position.Add (Speed.Multiply (Powerup)); std::cin.get (); }
有运算符重载的时候代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <string> struct Vector2 { float x, y; Vector2 (float x, float y) : x (x), y (y) {} Vector2 Add (const Vector2& other) const { return Vector2 (x + other.x, y + other.y); } Vector2 operator +(const Vector2& other) const { return Add (other); } Vector2 Multiply (const Vector2& other) const { return Vector2 (x * other.x, y * other.y); } Vector2 operator *(const Vector2& other) const { return Multiply (other); } };int main () { Vector2 position (4.0f , 4.0f ) ; Vector2 Speed (0.5f , 1.5f ) ; Vector2 Powerup (1.1f , 1.1f ) ; Vector2 result1 = position.Add (Speed.Multiply (Powerup)); Vector2 result2 = position + Speed * Powerup; std::cin.get (); }
3. 运算符重载 << 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <iostream> #include <string> struct Vector2 { float x, y; Vector2 (float x, float y) : x (x), y (y) {} Vector2 Add (const Vector2& other) const { return Vector2 (x + other.x, y + other.y); } Vector2 operator +(const Vector2& other) const { return Add (other); } Vector2 Multiply (const Vector2& other) const { return Vector2 (x * other.x, y * other.y); } Vector2 operator *(const Vector2& other) const { return Multiply (other); } }; std::ostream& operator <<(std::ostream& stream, const Vector2& other) { stream << other.x << ", " << other.y; return stream; }int main () { Vector2 position (4.0f , 4.0f ) ; Vector2 Speed (0.5f , 1.5f ) ; Vector2 Powerup (1.1f , 1.1f ) ; Vector2 result1 = position.Add (Speed.Multiply (Powerup)); Vector2 result2 = position + Speed * Powerup; std::cout << result2 << std::endl; std::cin.get (); }
4. 运算符重载 == and != 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #include <iostream> #include <string> struct Vector2 { float x, y; Vector2 (float x, float y) : x (x), y (y) {} Vector2 Add (const Vector2& other) const { return Vector2 (x + other.x, y + other.y); } Vector2 operator +(const Vector2& other) const { return Add (other); } Vector2 Multiply (const Vector2& other) const { return Vector2 (x * other.x, y * other.y); } Vector2 operator *(const Vector2& other) const { return Multiply (other); } bool operator ==(const Vector2& other) const { return x == other.x && y == other.y; } bool operator !=(const Vector2& other) const { return !(*this == other); } }; std::ostream& operator <<(std::ostream& stream, const Vector2& other) { stream << other.x << ", " << other.y; return stream; }int main () { Vector2 position (4.0f , 4.0f ) ; Vector2 Speed (0.5f , 1.5f ) ; Vector2 Powerup (1.1f , 1.1f ) ; Vector2 result1 = position.Add (Speed.Multiply (Powerup)); Vector2 result2 = position + Speed * Powerup; std::cout << result1 << std::endl; std::cout << result2 << std::endl; if (result1 == result2) { std::cout << "equality" << std::endl; } if (result1 != result2) { std::cout << "not equality" << std::endl; } std::cin.get (); }
42. The “this” keyword in C++ ⭐ C++中有这样一个关键字this ,通过它可以访问成员函数。 this
是一个指向当前对象实例的指针,该method (方法)属于这个对象实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> class Entity ;void PrintEntity (Entity* e) ;class Entity {public : int x, y; Entity (int x, int y) { this ->x = x; this ->y = y; PrintEntity (this ); } int GetX () const { const Entity* e = this ; return this ->x; } };void PrintEntity (Entity* e) { }int main () { std::cin.get (); }
43. Obeject lifetime in C++ (Stack/Scope lifetimes)⭐
scope: 作用域
1. 基于stack和基于heap的变量在对象生存期上的区别
基于stack的变量在一出作用域,该变量所占用的内存空间便被释放了;
基于heap的变量只要不手动释放内存空间,则该内存空间便不会被释放,知道程序的结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> #include <string> class Entity {public : Entity () { std::cout << "Created Entity!" << std::endl; } ~Entity () { std::cout << "Destoryed Entity!" << std::endl; } };int main () { { Entity e; } std::cin.get (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <string> class Entity {public : Entity () { std::cout << "Created Entity!" << std::endl; } ~Entity () { std::cout << "Destoryed Entity!" << std::endl; } };int main () { { Entity* e = new Entity (); } std::cin.get (); }
举个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> int * CreatedArray () { int array[50 ]; return array; }int main () { int * a = CreatedArray (); std::cin.get (); }
改正方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> int * CreatedArray () { int * array = new int [50 ]; return array; }int main () { int * a = CreatedArray (); std::cin.get (); }
2. scope pointer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <iostream> #include <string> class Entity {public : Entity () { std::cout << "Created Entity!" << std::endl; } ~Entity () { std::cout << "Destoryed Entity!" << std::endl; } };class ScopePtr {private : Entity* m_Ptr;public : ScopePtr (Entity* ptr) : m_Ptr (ptr) {} ~ScopePtr () { delete m_Ptr; } };int main () { { ScopePtr e = new Entity (); } std::cin.get (); }
44. SMART POINTERS in C++ (std::unique_ptr, std::shared_ptr, std::weak_ptr)
smart pointers使得new-delete的过程自动化
1. unique_ptr—scope pointer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <iostream> #include <string> #include <memory> class Entity {public : Entity () { std::cout << "Created Entity!" << std::endl; } ~Entity () { std::cout << "Destoryed Entity!" << std::endl; } void Print () { std::cout << "Hello world!" << std::endl; } };int main () { { std::unique_ptr<Entity> entity = std::make_unique <Entity>(); entity->Print (); } std::cin.get (); }
一个更好 的做法是:
1 std::unique_ptr<Entity> entity = std::make_unique <Entity>();
这对于unique_ptr
来说很重要,主要原因是出于exception safety (异常安全),如果构造函数碰巧抛出异常,它会稍微安全一些。你不会最终得到一个没有引用的dangling pointer (悬空指针)而造成过内泄漏。
前面提到了unique_ptr
不能被复制。如果你去看它的定义,你会发现它的拷贝构造函数和拷贝构造操作符实际上被删除了,这就是为什么你运行如下代码时会编译错误。
2. shared_ptr
shared_ptr使用的是reference counting (引用计数).
举个例子,我刚创建了一个共享指针,又创建了另一个共享指针来复制它,此时我的引用计数是 2。第一个指针失效时,我的引用计数器减少 1,然后最后一个失效时,我的引用计数回到 0,就真的“dead”了,因此内存被释放。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> #include <string> #include <memory> class Entity {public : Entity () { std::cout << "Created Entity!" << std::endl; } ~Entity () { std::cout << "Destoryed Entity!" << std::endl; } void Print () { std::cout << "Hello world!" << std::endl; } };int main () { { std::shared_ptr<Entity> sharedEntity = std::make_shared <Entity>(); std::shared_ptr<Entity> e0 = sharedEntity; } std::cin.get (); }
有了共享指针,你当然可以进行复制。 下图代码中有两个作用域,可以看到里面这个作用域死亡时,这个 sharedEntity 失效了,然而并没有对 Entity 析构并删除,因为 e0 仍然是有效的,并且持有对该 Entity 的引用。再按一下 F10,当所有引用都没了,当所有追踪shared_ptr
的栈分配对象都死亡后,底层的 Entity 才会从内存中释放并删除。
3. weak_ptr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <string> #include <memory> class Entity {public : Entity () { std::cout << "Created Entity!" << std::endl; } ~Entity () { std::cout << "Destoryed Entity!" << std::endl; } void Print () { std::cout << "Hello world!" << std::endl; } };int main () { { std::weak_ptr<Entity> e0; { std::shared_ptr<Entity> sharedEntiy = std::make_shared <Entity>(); e0 = sharedEntiy; } } std::cin.get (); }
4. smart pointer and new-delete 这就是很有用的智能指针,但它们绝对没有完全取代new
和delete
关键字。只是当你要声明一个堆分配的对象而且不希望由自己来清理,这时候你就应该使用智能指针,尽量使用unique_ptr
,因为它有较低的开销。但如果你需要在对象之间共享,不能使用unique_ptr
的时候,就用shared_ptr
45. Copying and Copy constructors in C++ copy means: copy data and copy memory.
创建一个String类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <iostream> #include <string> class String {private : char * m_Buffer; unsigned int m_Size; public : String (const char * string) { m_Size = strlen (string); m_Buffer = new char [m_Size + 1 ]; memcpy (m_Buffer, string, m_Size + 1 ); } ~String () { delete [] m_Buffer; } friend std::ostream& operator <<(std::ostream& stream, const String& string); }; std::ostream& operator <<(std::ostream& stream, const String& string) { stream << string.m_Buffer; return stream; }int main () { String string = "Cherno" ; std::cout << string << std::endl; std::cin.get (); }
1. shallow copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> #include <string> class String {private : char * m_Buffer; unsigned int m_Size;public : String (const char * string) { m_Size = strlen (string); m_Buffer = new char [m_Size + 1 ]; memcpy (m_Buffer, string, m_Size + 1 ); } ~String () { delete [] m_Buffer; } friend std::ostream& operator <<(std::ostream& stream, const String& string); }; std::ostream& operator <<(std::ostream& stream, const String& string) { stream << string.m_Buffer; return stream; }int main () { String string = "Cherno" ; String second = string; std::cout << string << std::endl; std::cout << second << std::endl; std::cin.get (); }
现在问题来了,内存中有两个 String,因为它们直接进行了复制,这种复制被称为shallow copy (浅拷贝)。它所做的是复制这个 char,内存中的两个 String 对象有相同的 char 的值,换句话说就是有相同的内存地址。这个 m_Buffer 的内存地址,对于这两个 String 对象来说是相同的,所以程序会崩溃的原因是当我们到达作用域的尽头时,这两个 String 都被销毁了,析构函数会被调用,然后执行delete[] m_Buffer
两次,程序试图两次释放同一个内存块。这就是为什么程序会崩溃——因为内存已经释放了,不再是我们的了,我们无法再次释放它。
2. deep copy — copy constructor 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include <iostream> #include <string> class String {private : char * m_Buffer; unsigned int m_Size;public : String (const char * string) { m_Size = strlen (string); m_Buffer = new char [m_Size + 1 ]; memcpy (m_Buffer, string, m_Size + 1 ); } ~String () { delete [] m_Buffer; } String (const String& other) : m_Size (other.m_Size) { std::cout << "Copied String!" << std::endl; m_Buffer = new char [m_Size + 1 ]; memcpy (m_Buffer, other.m_Buffer, m_Size + 1 ); } char & operator [](unsigned int index) { return m_Buffer[index]; } friend std::ostream& operator <<(std::ostream& stream, const String& string); }; std::ostream& operator <<(std::ostream& stream, const String& string) { stream << string.m_Buffer; return stream; }void PrintString (const String& string) { std::cout << string << std::endl; }int main () { String string = "Cherno" ; String second = string; second[2 ] = 'a' ; PrintString (string); PrintString (second); std::cin.get (); }
46. The Arrow Operator in C++ 1. pointer, reference, arrow 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> #include <string> class Entity {public : void Print () const { std::cout << "Hello!" << std::endl; } };int main () { Entity e; e.Print (); Entity* ptr = &e; (*ptr).Print (); Entity* ptr1 = &e; Entity& entity = *ptr1; entity.Print (); Entity* ptr2 = &e; ptr2->Print (); std::cin.get (); }
2. overloading 箭头作为一种运算符,C++可以重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <string> class Entity {public : void Print () const { std::cout << "Hello!" << std::endl; } };class ScopedPtr {private : Entity* m_Obj;public : ScopedPtr (Entity* entity) :m_Obj (entity) { } ~ScopedPtr () { delete m_Obj; } Entity* operator ->() { return m_Obj; } const Entity* operator ->() const { return m_Obj; } };int main () { ScopedPtr entity = new Entity (); entity->Print (); std::cin.get (); }
3. offset 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <string> struct Vector3 { float x, y, z; };int main () { int offsetx = (int )&((Vector3*)nullptr )->x; std::cout << offsetx << std::endl; int offsety = (int )&((Vector3*)nullptr )->y; std::cout << offsety << std::endl; int offsetz = (int )&((Vector3*)nullptr )->z; std::cout << offsetz << std::endl; std::cin.get (); }
附:编程习惯 A. m_ C++中m_的含义是什么? |21xrx.com
在C++中,m_是一种命名约定,通常被用于表示一个类的成员变量。m_的含义是”member variable”或者”成员变量”,是为了区分成员变量和其他类型的变量而引入的。
使用m_的好处是可以方便地区分成员变量和其他变量,使代码变得更加可读和易于理解。此外,m_还可以避免与全局变量、局部变量或其他变量混淆,从而避免出现代码错误。
在使用m_时,需要注意以下几点:
\1. m_只是一种命名约定,不是C++的关键字或保留字,因此在使用时不要将其与其他变量名混淆。
\2. 使用m_时应该遵循统一的规范,例如将所有成员变量都以m_为前缀命名。
\3. 在构造函数和析构函数中,应该将所有成员变量的初始值或释放操作放在一起,以方便管理。
\4. 注意,在使用m_时应该尽可能使用访问器(getter和setter)而不是直接访问成员变量,这样可以使代码更加可维护和易于修改。
总的来说,m_是一种很好的命名约定,可以使代码更加清晰和易于理解。在编写C++代码时,使用m_能够提高代码的可读性和可维护性,值得开发者们好好利用。
B. 命名规则 1. 驼峰 原始:user login count
驼峰:userLoginCount
2. 帕斯卡 原始:user login count
帕斯卡:UserLoginCount
3. 蛇形 原始:user login count
蛇形:user_login_count
4. 匈牙利 int g_i32tempuratureValue
:全局 32位有符号整型变量
float l_f32tempuratureValue
:局部 32位有符号整型变量
unsigned char s_u8tempuratureValue
:静态 无符号字符型变量
参考:内容目录 — Google 开源项目风格指南 (zh-google-styleguide.readthedocs.io)