P5:编译器会把每个cpp文件都编译成一个obj文件,而我们的项目则被编译成一个可执行文件(exe)。而每个cpp文件或者每个定义和申明之间是因为编译器可以自动链接(linking)他们。
P6:头文件就是编译器在欲编译时把头文件的内容复制过来。所以在我们的头文件中,尽量不要去定义函数,否则会导致错误。
P8:在window32程序下char型占用一个字节,short2个,int4个,long long8个,其中char型实际上存储也是数字,因为是根据ASCII表存储的。float占用四个字节,double占用8个,而对于bool只用一个字节,其是存储的只是0或1,那为什么只给他1bit呢,因为我们的电脑在寻址的时候最小单位就是字节。数据类型本质上都是一样的,他们的区别在于他们的所占内存的大小。
P10:为了头文件中的定义不被重复复制到编译单元(cpp)中,我们需要做保护机制,之前常用的方式是在我们的头文件中加入预处理指令,比如
1 #ifndef _LOG_H 2 #define _LOG_H 3 4 void log(const char* message); 5 #endif
但是现在我可以用#pragma once在头文件中就可以了 #pragma once
P17:对于引用变量,其实只是一个我们引用变量的一个别名,他在内存中没有实际的存储,我们可以把他当成其变量一样使用。
P19:类和结构本质上没什么区别,唯一的区别就是类默认是私有的,不管是继承还是成员。而结构是公开的。所以结构之所以存在是因为和c兼容,因为c中没有类,只有结构。习惯中我们使用结构来存储一些数据,而至于其他的尽量使用类。
对于为什么要用指针参数或者引用参数主要有两点:
1、我们可以修改调用函数中的数据对象
2、因为他们只是一个地址或者一个别名,而不需要拷贝副本,所以提高了程序的运行速度
对于合适用值传递、指针传递、引用传递
对于使用传递的值而不作修改的函数:
- 如果数据对象较小,如内置数据类型或者小型结构,则按值传递。
- 如果数据对象是数组,则只能使用指针,并且将指针声明为指向const的指针。
- 如果数据对象较大,则使用const指针或者const引用,节省复制结构所需的时间和空间。
- 如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用。这是C++增添引用这个特性的主要原因。
对于修改调用函数中数据的函数
- 如果数据对象是内置数据类型,则使用指针。
- 如果数据对象是数组,则只能使用指针。
- 如果数据对象是结构,则使用引用或者指针。
- 如果数据对象是类对象,则使用引用。
使用引用参数,应尽可能使用const
- 使用const可以避免无意中修改数据的编程错误。
- 使用const使得函数能够处理const和非const实参。否则将只能接收非const数据。
- 使用const引用使函数能够正确生成并使用临时变量。
P21:对static:一、如果在类或者结构外边,他的主要作用就是这个数据类型只是在这个范围内。比如静态局部变量的作用域只是在函数内部,而静态全局变量作用域这个文件,静态函数也是这个文件。当然因为静态数据类型是存储在常量和静态区的所以他会一直存在到程序结束。二、对于成员数据类型,主要作用就是不管是这个成员函数还是变量他都是这个类的实例所共享的。所以他的数据作用于整个类,比如定义一个静态成员变量,那么他的值不会随着新的实例而重新初始化。因为他储存在静态区域,不会随着类的实例重新分配内存。
另外extern是他会去寻找外部的数据类型。
1 struct Entity 2 { 3 static int x, y; 4 5 void Print() 6 { 7 std::cout << x << "," << y << std::endl; 8 } 9 }; 10 11 int Entity::x; //静态变量初始化 12 int Entity::y; 13 int main() 14 { 15 Entity e; 16 e.x = 2; //我们这里因为是静态变量跟我们的实例e没有关系,可以写成 Entity::x = 2 17 e.y = 3; 18 19 Entity e1; 20 e1.x = 5; //我们这里因为是静态变量跟我们的实例e1没有关系,可以写成 Entity::x = 5 21 e1.y = 8; 22 23 e.Print(); //这里打印x,y是5和8 24 e1.Print(); //这里是打印x,y也是5和8 25 26 return 0; 27 }
最后静态方法不能访问非静态变量和方法,因为他们不是确定的,是随着实例的变化而变化的。相反却可以。当然静态方法是可以访问静态变量的。因为静态变量是确定的。
P24:枚举Enum
1 enum Example 2 { 3 A, B, C //这里可以不指定值,他会默认为0,1,2 4 }; 5 6 enum Example1 7 { 8 A1 = 5, B2 = 7, C3 //这里也可以指定值,他会按照你给定的值递增 9 }; 10 11 int main() 12 { 13 14 Example value = B; //给value分配值,实际上只能分配枚举里面的值 15 if (value == 1) //而这里value也就是B在枚举中是1,因为他是从0开始递增的,A是0 16 { 17 std::cout << "B=" << value << std::endl; 18 } 19 Example1 value1 = C3; 20 if (value1 == 8) 21 { 22 std::cout << "C3=" << value1 << std::endl; 23 } 24 std::cin.get(); 25 return 0; 26 }
结果为:B=1 C3=8
P28:虚函数
1 class Entity 2 { 3 public: 4 virtual std::string GetName() { return "Entity"; } //定义虚函数 5 }; 6 7 class Player : public Entity 8 { 9 private: 10 std::string m_Name; 11 public: 12 Player(const std::string& name) 13 :m_Name(name) {} 14 std::string GetName() override { return m_Name; } //这里函数进行了覆盖,override写不写都ok,但是为了可读性,还是写一下比较好 15 }; 16 17 void PrintName(Entity* Entity) 18 { 19 std::cout << Entity->GetName() << std::endl; 20 } 21 22 23 int main() 24 { 25 Entity *e = new Entity; 26 std::cout << e->GetName() << std::endl; 27 28 Player* p = new Player("xiaodang"); 29 PrintName(p); 30 std::cin.get(); 31 return 0; 32 }
P29:纯虚函数或者接口
1 class Printable //抽象类或者叫他接口 2 { 3 public: 4 virtual std::string GetClassName() = 0; //纯虚函数 5 }; 6 7 class Entity : public Printable 8 { 9 public: 10 virtual std::string GetName() { return "Entity"; } //虚函数 11 std::string GetClassName() override { return "Entity"; } 12 }; 13 14 class Player : public Entity 15 { 16 private: 17 std::string m_Name; 18 public: 19 Player(const std::string& name) 20 :m_Name(name) {} 21 std::string GetName() override { return m_Name; } //覆盖 22 std::string GetClassName() override { return "Player"; } //覆盖 23 }; 24 25 void PrintName(Entity* Entity) 26 { 27 std::cout << Entity->GetName() << std::endl; 28 } 29 30 void Print(Printable* obj) 31 { 32 std::cout << obj->GetClassName() << std::endl; 33 } 34 35 int main() 36 { 37 Entity* e = new Entity; 38 39 Player* p = new Player("xiaodang"); 40 41 Print(e); 42 Print(p); 43 delete e; 44 delete p; 45 std::cin.get(); 46 return 0; 47 }
P34:对于Const主要作用就是限定这是个常量,无法改变,仅仅只读。
1 int main() 2 { 3 const int MAX_AGE = 90; 4 5 const int* a = new int; 6 *a = 2; //代码报错,因为我们使用const,代表*a的值不可改变 7 a = (int*)&MAX_AGE; //代码正确,因为我们改变的是a的地址,而const限定的是*a 8 9 int* const b = new int; 10 *b = 2; //代码正确,因为我们使用的const没有限制*b 11 b = (int*)&MAX_AGE; //代码错误,因为我们改变的是b的地址,而const限定了b的地址 12 13 std::cin.get(); 14 return 0; 15 }
1 class Entity 2 { 3 private: 4 int m_X, m_y; 5 public: 6 int GetX() const 7 { 8 m_X = 2; //代码错误,因为有const的限制,我们只能读取而不能修改 9 return m_X; //代码正确 10 } 11 };
1 class Entity 2 { 3 private: 4 std::string e_Name; 5 int e_DebugCount = 0; 6 mutable int e_DebugCount_temp = 0; 7 public: 8 const std::string &GetName() const 9 { 10 e_DebugCount++; //代码错误,因为我们进行了const 11 e_DebugCount_temp++; //代码正确,因为我们使用了mutable 12 return e_Name; 13 } 14 };
mutable的作用是让变量可变的,是代码更加整洁。用法有点像const。
PS:多态指在基类的方法前面有一个virtual,在派生类中重写该函数,程序运行中则根据父类类型指向不同类型(子类或者父类)的对象来调用父类或者基类的虚函数。
为什么使用多态?
1、实现代码的复用,避免代码的冗余;
2、减少代码之间的关联性,即耦合度,方便后期对代码的修改,功能的改善,不必牵一发而动全身,减少不必要的麻烦;
3、能够通过重写子类的方法,使不同的对像具有不同的功能,扩展了功能。
纯虚函数是在虚函数后面去掉定义直接“=0”,所以有纯虚函数的类也叫抽象类,他不能实例化,而对于他的派生类必须重写这个纯虚函数。也是为了更好的实现多态。
满足下面条件的c++类则称为接口
1、类中没有定义任何的成员变量
2、所有的成员函数都是公有的
3、所有的成员函数都是纯虚函数
4、接口是一种特殊的抽象类
P36:构造函数初始化
1 class Entity 2 { 3 private: 4 std::string m_Name; 5 public: 6 Entity() 7 { 8 m_Name = "xiaodang"; 9 } 10 Entity(std::string name) 11 { 12 m_Name = name; 13 } 14 std::string GetName() 15 { 16 return m_Name; 17 } 18 }; 19 20 int main() 21 { 22 Entity e; 23 e.GetName(); 24 25 Entity o("xiaozhang"); 26 e.GetName(); 27 std::cout << e.GetName() << std::endl; 28 std::cout << o.GetName() << std::endl; 29 std::cin.get(); 30 return 0; 31 }
当然我们还可以写成这样:这样更加节省性能
1 class Entity 2 { 3 private: 4 std::string m_Name; 5 public: 6 Entity() 7 :m_Name("xiaodang") {} //这样写跟上面的例子是完全一样的,如果我们多个参数只需要按照顺序排列好就可以了 8 9 Entity() 10 { //这里可以写成 m_Name = std::string("xiaodang") 所以如果用=的方法相当于我们创建两块内存,所以相对浪费性能 11 m_Name = "xiaodang"; 12 } 13 14 Entity(std::string name) 15 :m_Name(name) {} 16 17 std::string GetName() 18 { 19 return m_Name; 20 } 21 };
P37:三元运算符
1 static int s_Level = 8; 2 static int s_Speed = 2; 3 4 int main() 5 { 6 if (s_Level > 5) 7 s_Speed = 10; 8 else 9 s_Speed = 5; 10 11 s_Speed = s_Level > 5 ? 10 : 5; //显示这种写法比上面的写法更好一点 12 13 s_Speed = s_Level > 5 ? s_Level > 10 ? 15 : 10 : 5; //这个的意思是s_Level跟5还有10进行比较,如果大于于5和10则是15,在他们中间则是10,都小于则是5 14 15 std::cout << s_Speed << std::endl; 16 std::cin.get(); 17 return 0; 18 }
P38:实例化对象
1 using String = std::string; 2 3 class Entity 4 { 5 private: 6 String m_Name; 7 public: 8 Entity() : m_Name("Unknown") {} 9 Entity(const String& name) : m_Name(name) {} 10 11 const String& GetName() const { return m_Name; } 12 }; 13 14 int main() 15 { 16 Entity entity; //在栈上创建一个Entity类型的对象,当然我们也可以写成 Entity entity = Entity(); 但是我们通常会简写 17 18 Entity* en = new Entity("xiaodang"); //在堆上创建一个Entity类型的对象,当然我们还需要去释放他 19 20 std::cout << en->GetName() << std::endl; 21 22 std::cin.get(); 23 delete en; 24 return 0; 25 }
P39:new关键字
1 Entity* e_One = new Entity(); //这两个唯一的区别就是e_Two没有调用构造函数,只是分配了内存 2 Entity* e_Two = (Entity*)malloc(sizeof(Entity));
P42: this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数
this指针的使用:一种情况就是,在类的非静态成员函数中返回类对象本身的时候,直接使用 return *this;另外一种情况是当参数与成员变量名相同时,如this->n = n (不能写成n = n)。
P44:智能指针(unique_ptr、shared_ptr、weak_ptr)智能指针都定义在memory头文件中。
唯一指针(unique_ptr): std::unique_ptr<std::string> p1; 只能指向一个对象,所以不支持普通的拷贝和赋值操作,虽然我们不能拷贝或者赋值unique_ptr,但是可以通过调用release或reset将指针所有权从一个(非const)unique_ptr转移给另一个unique
1 std::unique_ptr<std::string> p1 ; 2 std::unique_ptr<std::string> p2 ; 3 4 p1.release(); //p1放弃对指针的控制权,返回指针,并将p1置为空 5 p1.reset(); //释放p1指向的对象 6 p1.reset(p2.release()); //如果提供了指针p2,令p1指向这个对象,否则将p1置为空
最安全的分配和使用动态内存的方法就是调用一个名为make_shared的标准库函数,此函数在动态内存中分配一个对象并初始化它,返回指向此对象的unique_ptr。
1 std::unique_ptr<std::string> p1 = std::make_unique<std::string>(); 2 std::unique_ptr<std::string> p2 = std::make_unique<std::string>();
共享指针(shared_ptr):当进行拷贝和赋值时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时,计数器就会递减,一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
1 std::shared_ptr<std::string> p4; 2 { 3 std::shared_ptr<std::string> p3 = std::make_shared<std::string>(); 4 p4 = p3; 5 }
弱引用指针(weak_ptr):weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。
1 std::weak_ptr<std::string> p5; 2 { 3 std::shared_ptr<std::string> p6 = std::make_shared<std::string>(); 4 p5 = p6; //他在其作用域后就直接销毁了 5 }
P47:动态数据(向量(vector))
1 #include<vector> 2 3 using namespace std; 4 5 struct Vertex 6 { 7 float x, y, z; 8 }; 9 10 ostream& operator<<(ostream& stream, Vertex& vertex) 11 { 12 stream << vertex.x << "," << vertex.y << "," << vertex.z; 13 return stream; 14 } 15 16 int main() 17 { 18 vector<Vertex> vertices; 19 vertices.push_back({ 1,2,3 }); 20 vertices.push_back({ 4,5,6 }); 21 for (int i = 0; i < vertices.size(); i++) 22 { 23 cout << vertices[i] << endl; 24 } 25 std::cin.get(); 26 return 0; 27 }
P52:处理多个函数返回值
1、独立定义表示结果的结构体或类:
2、使用tuple+structure binding
公有接口不推荐,私有实现可采用。相较方式1可读性可维护性弱,但可省去单独的定义。
3、使用输入输出参数
最差的方式,多见于老旧代码,可读性可维护性最差,无法清晰表达哪些是输入哪些是输出。且要求输入输出参数有默认构造。且会导致调用方在调用函数前定义相关变量。
4、如果只是想增加一个函数的执行结果状态,可以使用optional。
1 #include<iostream> 2 #include<tuple> 3 #include<string> 4 5 using namespace std; 6 7 tuple<int, string>GetReturn() 8 { 9 return make_tuple(2019, "xiaodang"); 10 } 11 12 int main() 13 { 14 int x; 15 string y; 16 tie(x, y) = GetReturn(); 17 cout << x << "和" << y << endl; 18 cin.get(); 19 return 0; 20 }
P53:模板(template)
1 template<typename T> 2 void GetPrintf(T value) 3 { 4 cout << value << endl; 5 } 6 7 int main() 8 { 9 GetPrintf<int>(25); 10 GetPrintf<string>("Hello"); 11 cin.get(); 12 return 0; 13 }
P55:宏
typedef和define都是替一个对象取一个别名,以此增强程序的可读性,区别如下:
(1)原理不同
#define是C语言中定义的语法,是预处理指令,在预处理时进行简单而机械的字符串替换,不作正确性检查,只有在编译已被展开的源程序时才会发现可能的错误并报错。
typedef是关键字,在编译时处理,有类型检查功能。它在自己的作用域内给一个已经存在的类型一个别名,但不能在一个函数定义里面使用typedef。用typedef定义数组、指针、结构等类型会带来很大的方便,不仅使程序书写简单,也使意义明确,增强可读性。
(2)功能不同
typedef用来定义类型的别名,起到类型易于记忆的功能。另一个功能是定义机器无关的类型。如定义一个REAL的浮点类型,在目标机器上它可以获得最高的精度:typedef long double REAL, 在不支持long double的机器上,看起来是这样的,typedef double REAL,在不支持double的机器上,是这样的,typedef float REAL
#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。
(3)作用域不同
#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用,而typedef有自己的作用域。
P59:Lambda
Lambda 的语法形式如下:
[capture](parameters)->return-type{body}
[] //未定义变量,试图使用在Lmabda内使用任何外部变量都是错误的 [x, &y] //x按值捕获,y按引用捕获 [&] //用到的任何外部变量都隐式的用引用捕获 [=] //用到的任何外部变量都隐式的用值捕获 [&, x] //x显式的用值捕获,其他变量用引用捕获 [=. &z] //z按引用捕获,其他的用值捕获 [&, a, b]//出a和b用值捕获,其他用引用捕获 [this] //函数体内可以使用所在类中的成员变量
[&a, b](int z)->int{ z = a + b; return z; }
1 #include<iostream> 2 #include<vector> 3 #include <algorithm> 4 using namespace std; 5 int main(void) 6 { 7 std::vector<int> v = { 1, 2, 3, 4, 5 }; 8 std::for_each(v.begin(), v.end(), [](int element) { cout << element << endl; }); 9 cin.get(); 10 return 0; 11 }
P62:线程(Thread)
1 #include<iostream> 2 #include<thread> 3 4 using namespace std; 5 6 static bool s_Working = false; 7 8 void DoWork() 9 { 10 using namespace std::literals::chrono_literals; 11 cout << this_thread::get_id() << endl; 12 while (!s_Working) 13 { 14 cout << "Hello World" << endl; 15 std::this_thread::sleep_for(1s); 16 } 17 } 18 19 int main() 20 { 21 thread worker(DoWork); //开启一个线程 22 cin.get(); 23 s_Working = true; 24 worker.join(); //线程的终结 25 cin.get(); 26 return 0; 27 }
P63:计时
1 #include <iostream> 2 #include <chrono> 3 #include <thread> 4 5 struct Timer 6 { 7 std::chrono::time_point<std::chrono::steady_clock> start, end; 8 std::chrono::duration<float> duration; 9 Timer() 10 { 11 start = std::chrono::high_resolution_clock::now(); 12 } 13 ~Timer() 14 { 15 end = std::chrono::high_resolution_clock::now(); 16 duration = end - start; 17 18 float ms = duration.count() * 1000.0f; 19 std::cout << "Time took " << ms << " ms" << std::endl; 20 } 21 }; 22 23 void GetTime() 24 { 25 Timer time; 26 for (int i = 0; i < 100; i++) 27 { 28 std::cout << "Hello World" << std::endl; 29 } 30 } 31 32 int main() 33 { 34 GetTime(); 35 std::cin.get(); 36 return 0; 37 }
P65:排序
1 int main() 2 { 3 std::vector<int> value = { 1,4,2,3,5 }; 4 //std::sort(value.begin(), value.end(), std::greater<int>()); 5 6 std::sort(value.begin(), value.end(), [](int a, int b) {return a > b; }); //可以使用stl中的模板,也可以利用lambda表达式来倒序排列 7 8 for (int values : value) //对于value集合进行循环 9 std::cout << values ; 10 11 std::cin.get(); 12 return 0; 13 }
P66:Type Punning(类型双关)
P67:union(联合)
union即为联合,它是一种特殊的类。通过关键字union进行定义,一个union可以有多个数据成员。
1、在任意时刻,联合中只能有一个数据成员可以有值。当给联合中某个成员赋值之后,该联合中的其它成员就变成未定义状态了。
2、联合的存储空间至少能够容纳其最大的数据成员。也可以为联合的成员指定长度。通过冒号操作符来实现成员长度的指定。
3、union不能含有引用类型的成员,默认情况下,union的成员都是公有的,这一点和struct相同
4、union既不能继承自其他类,也不能作为基类使用,所以在union中不能含有虚函数。
1 int main() 2 { 3 union 4 { 5 int a; 6 char c; 7 double b; 8 }; 9 10 a = 20; 11 b = 45.6; 12 c = 'H'; 13 std::cout << "a = " << &a <<std::endl; 14 std::cout << "b = " << &b << std::endl; 15 std::cout << "c = " << &c << std::endl; //三个结果都指向同一个地址 16 std::cin.get(); 17 return 0; 18 }
P69:类型转换
static_cast、dynamic_cast、const_cast和reinterpret_cast
P75:结构化绑定