The Cherno C++ 教程 笔记 自用

The Cherno C++ 教程 笔记 原视频 b站搬运 仅仅自用知乎已经有很多优秀的笔记了

C++ 如何工作

建一个project 在 资源文件(source)添加cpp文件

源文件

#后面是预处理语句 # include <iostream> 编译器会把iostream 和我们的代码黏起来

<< 重载运算符

debug 会默认关掉所有优化 release模式会更快 我们可以定制编译的是exe 还dll(库文件)

cpp 编译(会把预处理的头文件和你的cpp粘好一起编译) object file.obj linker(在属性里可以定义一些选项)合并多个obj(解析obj之间依赖的符号比如外部函数)为执行文件

ctrl +F7 单独编译当前文件所以不会有exe()

visual studio 存放很奇怪文件orz

我们查bug 主要看 output window error list是垃圾

申明 告知编译器函数存在

头文件可以用来放一堆申明函数

定义 定义一个函数要干什么

编译

在项目 属性 C/C++ 预处理器 ->预处理到文件设置 是 会得到 .j文件是预处理结束后的文件

#define INTEGER int C++会替换前面的词 为后面的词

1
2
3
#if  //判断是否为真决定是否执行代码

#endif

obj 文件是 二进制 我们可以用 项目属性 c/c++ 输出文件 汇编程序输出里 设置输出可读汇编语言

result=a*b

return result

和之间return result 在debug 和release模式是不一样的你可以查看汇编代码发现这一点

链接 主要焦点是找到每个符号和函数在哪里

static int Multiply

static 意味着该函数只在这个cpp翻译单元里定义

1
2
3
4
inline  void Log(const char* message)
{
std::cout<< message<< std::endl;
}

inline 会把Log函数替换为Log定义的内容

link c++需要一个入口点(Entry Point在 linker里面 ) 入口点不一定是main

C++变量

不同变量在c++中唯一的区别就是存储的大小

int 一般 4字节(依赖编译器)

unsigned int

char short int long long long

char a=65 char a='A'

其他类型赋值字符会变成数字 shot a='A' 打印为65

float variable= 5.5 //其实定义了双精度double

float variable= 5.5f//f可以大写也可以小写 表示定义的是浮点数

bool 1 byte 虽然理论是只要1bit

sizeof(variable) 返回变量大小

指针(pointer)* 和引用(reference)&

c++ 函数

为了减少代码重复

调用函数 会使用堆栈结构

每次我们调用函数时,编译器生成call指令 创建一个堆栈结构 把参数这样的东西推进堆栈 把一个放回地址压入堆栈 在跳到二进制执行文件的不同部分执行函数指令 为了push回结果还要回到调用函数的地方 在内存里跳来跳去 所以有时候要用inline

c++头文件

#pragma once //pragma 编译之前先处理 once 只在一个翻译单元复制一次

1
2
3
4
5
6
7
8
#ifndef _LOG_H


#define _LOG_H
void InitLog();
strcut Player();
//如果_LOG_H没定义则运行定义这部分
#endif

引号可以指定相对路径的文件

尖括号只用与编译器包含的路径

visual studio 配置

在设置断点以后按F5 右键断点 go to disassembly 可以查看汇编

visual studio 显示的 解决方案目录不是真实的点击 显示所有文件可以看到真实的目录 可以建一个src 文件夹 把所有 cpp文件加入到里面

我们可以自定义visu studio 的输出文件和 中间文件目录 在项目的属性常规里

设置输出目录$(SolutionDir)bin\$(Platform)\$(Configuration)\

中间目录

$(SolutionDir)bin\intermediates\$(Platform)\$(Configuration)\

$(SolutionDir)这些宏的意义可以在编辑里查看

c++指针

指针是一个整数 存储地址的数字 指针的类型的类型是告诉编译器存的地址指的东西是包含多少字节的 这样可以在用*读取的时候配置适当内存 int*ptr指针表示 当我们用*ptr= 10; 要设置四字节的内存所以void *ptr指针 不能 *ptr=10

1
2
3
void* ptr = 0; // NULL nullptr 都是相同的
int var = 8;
void* ptr2 = &var;// & 取出 var的地址 ptr2 现在存着var的地址
1
2
3
4
5
char* buffer = new char[8];//申请一个 8字节的内存 记录它的第一个地址
memset(buffer, 0, 8);//填入 0 到8个字节

char** ptr = &buffer;//指向存地址的地址 你可以在内存了读取他的值 然后查找(要倒着输入是反着存的)会找到存0的8个字节
delete[] buffer; //删除buffer的内存

C++引用

引用本身不是变量

1
2
3
int&  //&是类型的一部分
int a = 5;
int& ref = a;//ref 是a的别名 要马上赋值

引用可以把变量传入函数 并改变它 指针也可以做到这一点但要注意改的是地址还是地址里的值(注意优先级 *value++ 改的是地址 (*value)++改的是值)

c++类

类只是对数据和功能组合在一起的一种方法。类不会带来新的功能 没有它也可以实现你想要到 它有点像语法糖 。

与结构体的对比

类一般默认是私有的 结构体默认是公开的

可以用#define struct class把struct 定义为 class 当public 和private要重写

static 有不同意义 取决上下文

一种在类和结构体外表示该变量只在他定义的翻译单元生效 另一种在类和结构体内 表示这个类和结构体的静态变量和方法 在所有实例里生效 改变一个所有实例都会改变 可以直接用类名加变量名或者函数名引用 和命名空间很类似比如 std::cout

同时在函数中使用static 可以延长变量的存在的周期 如果没有static 函数内变量会在 函数使用完后销毁 有了static 这个变量会在函数用完后还存在 且只能在函数内使用 不能再外部调用

extern var尝试从其他翻译单元找到变量var的定义

尽量用静态变量

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
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <iostream>
void Log(const char* message);
//#define struct class
class Player
{
public://为了可以在类外访问他们
int x, y;
int speed;


void Move(int xa, int ya)
{
x += xa * speed;
y += ya * speed;
}
void Print()
{
std::cout << x << ", " << y << std::endl;
}
};//有分号


class Singleton
{
private:
static Singleton* s_Instance;
public:
static Singleton& Get() { return *s_Instance; }
void Hello() {
Log("mmm");
}
};


class Singleton2
{

public:
static Singleton2& Get()
{
static Singleton2 instance; //如果没有用static 建立的实例会在函数定义的花括号外被销毁 要把引用static Singleton2& 改成复制static Singleton2
return instance;
}
void Hello() {
Log("mmmm");
}
};

Singleton* Singleton::s_Instance = nullptr;

int main()
{
Singleton::Get().Hello();
Singleton2::Get().Hello();
if (6 == 6)
Log("Hello World!");//可以加{} 也可以不加 esle if 其实是 else + if 所以可以写成
//else
// if (){
// }
//std::cout << " hello " << std::endl;
void* ptr = 0; // NULL nullptr 和0 都是相同的
int var = 8;
void* ptr2 = &var;// & 取出 var的地址 ptr2 现在存着var的地址
Player player;
player.x = 5;
player.y = 5;
Player player2 = { 8,7 };
player2.Print();
for (int i = 0; i < 5; i++)
{
if (i % 2 == 0)
continue;//跳过符合此次判断的i 如果是break直接跳出循环 如果是return 直接结束函数
Log("Hello");

char* buffer = new char[8];//申请一个 8字节的内存 记录它的第一个地址
memset(buffer, 0, 8);//填入 0 到8个字节

char** ptr = &buffer;//指向存地址的地址 你可以在内存了读取他的值 然后查找(要倒着输入是反着存的)会找到存0的8个字节
delete[] buffer; //删除buffer的内存
std::cout << i << std::endl;
}

std::cin.get();
}

c++ 枚举

1
2
3
4
5
enum MyEnum 
{
A,B,C
};
MyEnum test = A;

c++虚函数

可以复写继承类的虚函数实现特定功能 会生成v表来实现引入额外内存开销但是是可以接受的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Play
{
public:
virtual std::string GetName() { return "Play"; }
};

class Player : public Play
{
private:
std::string m_Name;
public:
Player(const std::string& name)
: m_Name(name) {}
std::string GetName() override{ return m_Name; }//override不是必要的但可以保证不出错
};
void PrintName(Play* e)
{
std::cout << e->GetName() << std::endl;//如果Play没有virtual虚函数那么GetName就不会被复写打印的总是Play
}

纯虚函数允许我们定义一个没有定义的函数然后强迫使用子类去实现它 不然不能实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Printable
{
public:
virtual std::string GetClassName() = 0;//纯虚函数
};


class classA : public Printable
{
std::string GetClassName() override { return "A"; }
};

void Printclass(Printable* obj)
{
std::cout << obj->GetClassName() << std::endl;
}

类中的可见性

private 只能在类中或者类的友元调用访问 protected 可以在子类和定义类调用但是不能在类外面用 public哪都行

1
2
3
int expmle[5];//在函数结束就会消失
int * ptr = example;
*(int*)((char*)ptr + 8) = 6 ; //等同与*(ptr +2 ) =6 c++中数组指针加一个数字等同于他代表的地址加乘以该指针类型大小之后的数字
1
2
int* another = new int[5]; //在堆上创建会一直在除非delete
delete[] another;
1
int* another = new int[5];//在类中定义会导致间接寻址类不是保存数组数据而是地址
1
2
3
4
#include <array>
int a[5];
int count = sizeof(a)/sizeof(int);
std::array<int,5> another;//another.size() 会有额外开销所以用原始数组会比较快

const

1
2
const char*  name="mkk";//字符串用双引号 字符用单引号 用const是因为这个字符串常常不能修改 要想有可以改的请定义字符数组 char name[]="mkk";
char name2[4]={'m','k','k',0};//最后打'\0'也行 这样打印name2会直打到mkk不会显示其他内存的东西
1
2
3
4
5
6
7
#include <string>
#include < stdlib.h>//可以用strlen得到长度但是如果中间有\0会得到到\0的长度
std::string name="mkk";// std::string("mkk") + "hello!";
name += " hello!";
bool contains = name.find("kk") != std::string:npos


1
2
3
4
void PrintString(const std::string& string)// const 表示承诺不修改变量 std::string&表示引用不是复制  直接std::string string 也行不过会额外复制数据造成开销
{
std::cout << string << std::endl;
}
1
2
3
4
const char* name = u8"mkk";//u8是不必须的每个字母1字节
const wchar_t* name2 = L"mkk";//windows 每个字母2字节 Linux 每个字母4字节 mac 可能有也是4
const char16_t* name3 = u"mkk";//每个字母2字节
const char32_t* name4 = U"mkk";//每个字母4字节
1
2
3
4
5
6
7
8
9
10
const int MAX_AGE = 90;
const int* a = new int;//和 int const* a = new int;一样 不能改变指针指向的值 但是可以改变指针本身即地址
*a = 2;//把指针指向2 不行因为定义了const指针
a = (int*)&MAX_AGE;//改变a本身即地址
int* const b = new int;//可以改变指向的东西但不能改变指向地址
*b = 2;//把指针指向2 可以
b = (int*)&MAX_AGE;//不可以
const int* const c = new int;//
*c = 2;//把指针指向2 不可以
c = (int*)&MAX_AGE;//不可以
1
2
3
4
5
6
7
8
9
10
11
12
13
class Entity
{
private:
int m_X, m_Y;
mutable int var;
public:
int GetX() const//仅在类中的用法表示改方法不会改变类成员的值
{
var=2; // 可以因为是mutable
// m_X = 10; 会报错
return m_X;
}
}
1
2
3
4
5
6
7
8
9
10
11
class Entity
{
private:
int* m_X, m_Y;//这里只有m_X是指针 m_Y不是 要想是 这样int* m_X, *m_y
public:
const int* const GetX() const//仅在类中的用法表示改方法不会改变类成员的值
{

return m_X;
}
}
1
2
3
4
void PrintEntity(const Entity& e)
{
std::cout << e.GetX() << std::endl;//要求GetX是一个const方法不让不行 另外类里面也可以有两个GetX方法一个是const的一个不是 这里会自动调用const的
}
1
2
3
4
5
6
7
8
9
10
11
int x = 8;
auto f = [=]() mutable // [=] 表示把值传进lambda函数f里面 所以正常情况下不能改变x
//加了mutable 相当于把x的值赋值给一个y然后外面y++ 在打印他不会改变外面的x函数
//[&]表示引用传递
{
x++;
std::cout << x << std::endl;

};
f();//9
std::cout << x << std::endl;//8

三元判断算符

python

1
x if (x > y) else y

c++

1
(x > y) ? x : y;

julia

1
(x > y) ? x : y;

new

在堆上创建 会比较慢 要主动delete 不然会一直在

1
2
3
4
5
6
7
8
9
int a = 2;
int* b = new int[50];


Entity* e = new Entity();//还可以写成这样Entity* e = new Entity[50];
//Entity* e = (Entity*)malloc(sizeof(Entity)); //和Entity* e = new Entity();相同不过malloc只是分配了内存没有分配构造函数

delete[] b;
delete e;

隐式转换

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
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("Unknown") , m_Age(age) {}

};
void PrintEntity(const Entity& entity)
{
//print
}

int main
{


PrintEntity(22);
PrintEntity(Entity("Cherno"));//PrintEntity("Cherno"); 注释的是不可行的
//因为要两次转换一次是从const char 到string 再从string到entity


Entity aa = Entity("mkk");//Entity aa = "mkk";//aa("mkk"); Entity aa = Entity("mkk");
//未注释的写法是一种隐式转换 在构造函数前面加上explicit可以禁止隐式转换 必须显式调用构造函数才可以比如Entity aa("mkk");
Entity bb = 22;//bb(22);Entity bb = Entity(22);
Entity* aaa = new Entity(std::string("mkk"));//直接 Entity* aaa = new Entity("mkk")不行
delete aaa;
}

运算符重载

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
struct Vector2
{
float x, y;
Vector2(float x, float )
: x(x) ,y(y) {}
Vector2 Add(const Vector2& other) const
{
return Vector2(x + other.x, y + other.y);
}
Vector2 operator+(const Vector2& other)
{
return Add(other);
}
bool operator==(const Vector2& other) const
{
return x == other.x && y == other.y;
}

};


std::ostream& operator<<(std::ostream& stream, const Vector2& other)
{
stream << other.x << "," << other.y ;
return stream;
}

int main()
{
Vector2 p(4.0f, 4.0f);
Vector2 s(0.5f, 1.1f);
Vector2 result1 = p + s;
std::cout << p << std::endl;
std::cout << result1 << std::endl;
std::cin.get();

}

this

this 是一个指向当前对象实例的指针,该方法属于这个对象的实例。

为了调用非静态方法,要一个实例化对象 这就要用到this

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
Void PrintEntity(const Entity& e)//类内要用先声明一下
class Entity
{
public:
int x,y;
Entity(int x ,int y)//这里的x和成员名一样直接x=x;不行 当然
//:x(x),y(y)可以
{
//Entity* const e =this;
this->x=x;
this->y=y;
Entity& e = *this;

PrintEntity(*this);

//delet this 可以但不要
}
int GetX() const
{
const Entity& e = *this;
}

}
Void PrintEntity(const Entity& e)
{
//Print
}

作用域指针

自动消除new 创建的东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ScopedPtr
{
private:
Entity* m_Ptr;
public:
ScopedPtr(Entity* ptr)
: m_Ptr(ptr)
{

}
~ScopedPtr()
{
delete m_Ptr;
}

};

int main()
{
{
ScopedPtr e = new Entity();

}//尽管用来了new 但是ScopedPtr 在作用域之后会自动销毁Entity
}

智能指针

让你摆脱new 和delete的方法

unique_ptr你不能复制他 一复制就消失一个

shared_ptr会计数你 的引用(复制或者共享) 当计数为0 释放内存

weak_ptr 不会增加引用计数其他和shared_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
{
//std::unique_ptr<Entity> entity(new Entity());
std::unique_ptr<Entity> entity = std::make_unique<Entity>();
//std::unique_ptr<Entity> e0 = entity; 不行
entity->Print();//(*entity).Print();


}

{
std::shared_ptr<Entity> e0;
{
std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>();

std::shared_ptr<Entity> e0 = sharedEntity;


}//e0还在 因为现在引用计数是1
}//e0会坚持到这里

{
std::weak_ptr<Entity> e1;
{
std::shared_ptr<Entity> sharedEntity1 = std::make_shared<Entity>();
e1 = sharedEntity1;
}//e1消失 因为weak_ptr不增加引用计数
}//e1不会坚持到这里

c++的复制与拷贝

1
2
int a =1;
int b =a;//ab是两个不同的的变量 有不同的地址
1
2
3
Vector2 * a= new Vector2();
Vector2 * b =a;
b->x=2;

箭头算符->

1
e->x=0;// (*e).x=0;
1
2
3
4
5
6
7
8
9
10
struct Vector3
{
float x, y,z;
};
int main()
{
int offset= (int)&((Vector3*)nullptr)->z;
std::cout<< offset << std:;endl;//x打印0 y打印4 z打印8
std::cin.get();
}

动态数组

Vector 其实是arraylist

一般很慢要优化

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
#include <iostream>
#include < string>
#include <vector>

struct Vertex
{
float x, y, z;
Vertex(float x, float y, float z)
: x(x), y(y), z(z)
{

}

Vertex(const Vertex& vertex)
: x(vertex.x) ,y(vertex.y),z(vertex.z)
{
std::cout << "copied!" << std::endl;
}

};


std::ostream& operator<<(std::ostream& stream, const Vertex& vertex)
{
stream << vertex.x << "," << vertex.y << ',' << vertex.z;
return stream;
}
void Function(const std::vector<Vertex>& vertices)
{

}

int main()
{
std::vector<Vertex> vertices;//尽量不要用指针分配因为这样可以在一条线上存储会更快
//当然如果一条线不够 要重新分配内存要复制会很慢这时候指针会好一些因为数据不在连续内存里

//vertices.reserve(3);告诉要分配多少内存这样在内存小于3时不用减少内存可以减少复制
vertices.push_back({ 1,2,3 });//会产生两次复制 一次是把在main函数栈上的变量复制到vecotor里面
//一次是调整大小的复制
vertices.push_back({16,73,9 });//vertices.emplace_back(16,73,9 );
//注释这段直接在vecotor那里建立变量所以不会增加复制

Function(vertices);
for (int i = 0; i < vertices.size(); i++)
std::cout << vertices[i] << std::endl;

vertices.erase(vertices.begin() + 1);

for (Vertex v : vertices) //Vetex& v : vertices
std::cout << v << std::endl;






std::cin.get();
}

优化技巧

vertices.reserve(3);

vertices.emplace_back(1,2,3);

c++使用库

库包含 库目录library和包含目录include 包含目录是一堆头文件 lib目录是预先构建的二进制文件

动态库和静态库区别是静态库已经包含在exe里面了 而动态库要是通过exe在运行时调用外部库函数要额外链接

https://www.glfw.org/

下你要的文件 32 或64

然后会用到include 和lib-vrc20??(选最新的)

在lib-vrc20??里面

glfw3.dll 运行时动态链接的库

glfw3dll.lib是一种静态库 和glfw3.dll一起用 (可以不用我们可以在glfw3.dll访问 但有它可以更快)

glfw3.lib静态库(你不想用其他两个dll可以链接它)

C++创建使用库

创建一个game项目 在里面创建src 创建Appliction.cpp 在game 添加一个Engine项目 然后设置属性 常规 配置类型为静态库 添加 Engine 一个src 创建Engine.h Engine.cpp

点击game解决方案属性 C/C++ 附属包含目录填入 $(SolutionDir)Engine\src; 点击game解决方案在添加里点引用勾选 Engine

C++返回多个值方法

写一个结构体返回(可以返回不同类型)

传入引用

传入指针 (可以传入nullptr 做一个检测决定是否改变值)

返回一个数组

Array会在栈上创建 Vector 会把它的底层存在堆上 所以 std::array会更快

返回元组tuple or pair

c++模板template

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>

template<typename T>//template<class T>也行
void Print(T value):
{
std::cout << value << std::endl;//template只有在你调用时才会实现
//所以如果后面没有用Print 你即使打错了value 为valu也不会报错;
}
template<typename T ,int N>
class Array
{
private:
T int m_Array[N];
public:
int GetSize() const { return N;}

};
int main
{
Print<int>(5);
Print(5.5f);
Array<int,5> array;
std::cout << array.GetSize() << std::endl;
std::cin.get();

}

mete programme

C++的堆与栈内存的比较

C++宏

这里主要讲在c++用预处理器来“宏”化一些操作。

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <string>

#define std::cin.get()
#define LOG(x) std::cout << x << std::endl
int main
{
LOG("hello");
WAIT;
}

在visual studio 里面Debug 模式定义 C/C++ preprocessor 里面 定义PR_DEBUG

Release 模式定义 C/C++ preprocessor 里面 定义PR_RELEASE可以利用下面的代码在不同模式选择是否打印

1
2
3
4
5
6
7
//#define PR_DEBUG=1 这也可在C/C++  preprocessor 设置
//#ifdef PR_DEBUG == 1
#ifdef PR_DEBUG
#define LOG(x) std::cout << x << std::endl
#esle
#define LOG(X)
#endif

\enter 的转义你可能在写很长的宏用上 后面别接回车

把new 用宏 定义为一个别的词 可以自动跟踪 哪些文件用了它 分配了多少内存

auto

可以自动计算出类型

1
2
int a = 5;
auto b = a;

我们是否应该到处使用auto?

什么时候该用 当你会改变函数类型时 你赋值 的变量是auto 你就不用改变它代码

auto 会使你搞不清楚变量类型 可读性差 特别是兼容性类型 你会不知道是char* 还是 std:string

在使用template 时 你不得不用模板因为你不知道类型

用auto替换超长的类型

1
2
3
4
5
6
7
8
std::vector<std::string> string;
strings.push_back("Apple");
string.push_back("orange");
for (auto it = string.begin(); it != strings.end() ; it++)
//for (std::vector<std::string>::iterator it = string.begin(); it != strings.end() ; it++)
{
std::cout << *it << std::endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Device {};
class DeviceManager
{
private:
std::unordered_map<std::string,std::vector<Device*>> m_Devices;
public:
const std::unordered_map<std::string,std::vector<Device*>>& GetDevices() const
{
return m_Devices;
}
};
int main()
{
//using DeviceMap = std::unordered_map<std::string,std::vector<Device*>>
//typedef std::unordered_map<std::string,std::vector<Device*>> DeviceMap
DeviceManager dm;
//const std::unordered_map<std::string,std::vector<Device*>>& devices = dm.GetDevices();
//const DeviceMap& devices = dm.GetDevices();
const auto& devices = dm.GetDevices();

}

c++的静态数组

std::array有边界检查(Debug时有) 普通数组没有

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <array>

int main()
{
std::array<int,5> data;
data[0] = 2;
data[4] = 1;

}

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>
#include <vector>

void Helloworld(int a )
{
std::cout << 'mkk'<<a<< std::endl;

}

void ForEach(const std::vector<int>& values,void(*func)(int))
{
for (int value: values)
func(value);
}
int main()
{

void(*mkk)(a) = Helloworld;
mkk(1);
typedef void(*Helloworldf)(int);

Helloworldf f1= Helloworld;
auto function = Helloworld;
function(1);
f1(1);
std::vector<int> values={ 1,5,6,7};
ForEach(values, [](int a){ std::cout << "value : " << a << std::endl;});
std::cin.get();
}

lambda

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <vector>
#include <functional>//std::function
#include <algorithm>//std::find_if
void ForEach(const std::vector<int>& values,const std::function<void(int)>& func)
{
for (int value: values)
func(value);
}
int main()
{
std::vector<int> values={ 1,5,6,7};
auto it = std::find_if(values.begin(),values.end(),[](int value){ return value > 3; });
std::cout << *it std::endl;
int a = 5 ;
auto lambda = [=](int value) mutable { a=111;std:: cout << "Value:" <<a << std::endl;};//[]表示捕获方式这里用于等值传入a mutable 让我们可以对a赋值


std::cin.get();

}

为什么不用 using namespace?

会很难分辨哪些是标准库的函数 同时外部的库使用 之间有同名函数也会混淆 甚至会出现没有编译错误找不到的bug

例子

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>
#inclued<string>

namespece apple
{
void print(const std ::string& text)
{
std::cout << text << std::endl;
}
}
namespece orange
{
void print(const char* text)
{
std::string temp =text;
std::reverse(temp.begin(),temp.end)
std::cout << temp << std::endl;
}
}

int main
{
using namespace apple;
using namespace orange;
print("Hello");//打印出olleH 因为oragne 定义的print 不用隐式转换所以会调用它 没有编译错误 你很难发现
}

c++的名称空间

感觉没什么用 不过可以嵌套好像有点不同 但是嵌套会不会太复杂了

1
2
3
namespace a=apple;
using namespace apple;
using apple::print;

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
#include <iostream>
#include <thread>

static bool s_Finished = false;

void Doworker()
{
using namespace std::literals::chrono_literals;
std::cout << "started thread id= " << std::this_thread::get_id() << std::endl;
while (!s_Finished)
{
std::cout << "working ..\n";
std::this_thread::sleep_for(1s);
}
}

int main()
{
std::thread worker(Doworker);
std::cin.get();
s_Finished = true;
worker.join();//让主线程等待工作线程完成工作
std::cout << "finshed" << std::endl;
std::cout << "started thread id= " << std::this_thread::get_id() << std::endl;
std::cin.get();

//started thread id = 18864
// working ..
// working ..
// working ..

// finshed
// started thread id = 27916

}

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
#include <iostream>
#include <chrono>

struct Timer
{
std::chrono::time_point<std::chrono::steady_clock> start, end;
std::chrono::duration<float> duration;
Timer()
{
start = std::chrono::high_resolution_clock::now();

}
~Timer()
{
end = std::chrono::high_resolution_clock::now();
duration = end - start;

float ms = duration.count() * 1000.0f;
std::cout << "Timer took" << ms << "ms" << std::endl;
}
};
void Function()
{
Timer timer;
for (int i = 0; i < 100; i++)
std::cout << "Hello\n";
}




int main()
{

Function();
std::cin.get();

}

在你计时的时候要了解代码是否被计算 比如下面的代码 在debug模式时 编译的结果程序是会一步一步循环然后输出value 但是release 模式 会优化代码 编译时就已经计算完运算的值了 直接在编译结果的程序输出value。

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
class Timer
{
Timer()
{
auto m_StartTimerpoint = std::chrono::high_resolution_clock::now();
}
~Timer()
{
stop();
}
void stop()
{
auto endTimepoint = std::chrono::high_resolution_clock::now();

auto start= std::chrono::time_point_cast<std::chrono::microseconds>(m_StartTimerpoint).time_since_epoch().count();
auto end= std::chrono::time_point_cast<std::chrono::microseconds>(endTimepoint).time_since_epoch().count();
auto duration = end -start;
double ms = duration * 0.001;

std::cout << duration<< "us ("<<ms<<"ms)\n";
}
private:
std:;chrono::time_point<std::chrono::high_resolution_clock> m_StartTimerpoint;


};
int main()
{
int value = 0;
{
Timer timer;
for (int i = 0 ;i < 10000000; i++)
value +=2;
}

std::cout<< value <<std::endl;
__debugbreak();//设置断点

}

C++多维数组

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
int** a2d = new int* [50];
for (int i = 0; i < 50; i++)
a2d[i] = new int[50];

int*** a3d = new int** [50];
for (int i = 0; i < 50; i++)
{
a3d[i] = new int* [50];
for (int j = 0; j < 50; j++)
{
a3d[i][j] = new int[50];//int** ptr = a3d[i];
//ptr[j] = new int[50];
}
}
a3d[0][0][0] = 0;
for (int i = 0; i < 50; i++)
delete[] a2d[i];

delete[] a2d;
for (int i = 0; i < 50; i++)
{

for (int j = 0; j < 50; j++)
{
delete[] a3d[i][j];
}
delete[] a3d[i];
}
delete[] a3d;

这样很慢 因为配置的内存不是连续的所以 会造成更多cache miss

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
#include <iostream>
#include<vector>
#include <algorithm>

int main()
{


std::vector<int> values = { 3,5,1,4,20 };
//std::sort(values.begin(), values.end(), std::greater<int>());最大的排最前面
std::sort(values.begin(), values.end(), [](int a, int b)
{
if (a == 1)
return false;//如果返回false 则a排到前面
if (b == 1)
return true;//如果返回false 则a排到前面
return a< b;//如果返回false 则
});
for (int value : values)
std::cout << value << std:: endl;

std::cin.get();


}

c++类型双关

把一段类型内存当另一个类型用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Entity
{
int x, int y;
};
int main()
{

int a = 50;
double& value = *(double)&a;// double value = *(double)&a;没有&直接复制会有问题
//很危险value = 0.0; 就会然程序崩溃 我们只有4字节却要存8字节
Entity e = {5, 8};
int* position = (int*)&e;

int y = *((char*)&e +4);

std::cout << position[0] << "," << position[1] <<std::endl;
std::cout << y <<std::endl;

std::cin.get();

}

c++union

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>
class Base
{
Public:
Base(){ std::cout << "Base Constructor\n";}
~Base(){std::cout << "Base Destructor\n";}//virtual ~Base(){std::cout << "Base Destructor\n";}//虚构析函数
}

class Derived : public Base
{
public:
Derived(){m_array= new int[5];std::cout << "Derived Constructor\n";}
~Derived(){delete[] m_array; std::cout << "Derived Destructor\n";}
private:
int* m_array;
};

int main()
{
Base* base = new Base();
delet baes;
std::cout<<"--------------\n";
Derived* derived = new Derived();
delete derived;
std::cout<<"--------------\n";
Base* poly = new Derived();
delete poly;//没有虚构析函数m_arrray 分配的内存就无法释放

}

c++类型转换

c语言风格类型转换

1
2
3
double value = 5.25;

double a = (int)value + 5.30;

c++风格类型转换

1
double s = static_cast<int>(value) +5.3;

和c功能没差别 但方便检索查看避错

static_cast reinterpret_cast(与类型双关有关) const_cast

dynamic_cast

使用时会存储 运行时类型信息(runtime typer information)RTTI

所以会有存储 RTTI 和读取RTTI的开销

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Entity
{
public:
virtual void PrintName(){}
};
class Player : public Entity
{
};
class Enemy : public Entity
{
};
int main()
{
Player* player = new Player();
Entity* actuallyPlayer = player;
Entity* actullayEnemy = new Enemy();

Player* p0= dynamic_cast<Player*>(actullayEnemy);//转换失败返回空指针
if(p0){};
Player* p1= dynamic_cast<Player*>(actullayPlayer);
}

预编译头文件

预先编译不会改动头文件为二进制可以减少以后你改动项目代码 编译时间

visual studio

创建一个pch.h 内容你要预编译的头文件

pch.cpp内容#include "pch.h"设置 属性 >c/c++>预编译头 >预编译头 选创建 以及 预编译头文件写pch.h

Main.cpp要有#include "pch.h"

项目属性 >c/c++>预编译头 >预编译头 选使用 以及 预编译头文件写pch.h

1
2
time g++ -std=c++11 pch.h 
time g++ -std=c++11 Main.cpp

OpenGL

一种跨平台的图形API 允许我们控制显卡 它的核心是一种规范

其他

一个c++参考网站 cppreference.com

Visual Studio如何设置才能支持C++11/14/17/20这些新标准的特性?

这里以最新的 Visual Studio 2022 来说明一下设置方法吧,毕竟 Community 版本都是可以免费使用的。

当新建一个C++项目后(比如新建了一个“C++控制台应用”项目),然后在“解决方案资源管理器”中右击项目,在下拉菜单中点击最下面的“属性”。在打开的“属性”窗口中,选择“配置属性 | C/C++ | 语言”,找到“C++ 语言标准”。可以看到它的默认选择是支持“ISO C++14标准”(如下图),当然C++14标准包含了C++11的特性

我们点开这个“C++ 语言标准”的下拉框,可以看到其它选择有“ISO C++14标准”“ISO C++17标准”“ISO C++20标准”等,按照需要选择其中一个就行了(提示:高版本的标准是包含低版本标准的特性的)。

如果选择了“ISO C++20标准”,还需要注意下面的问题

Visual C++ 2022 对 C++20标准中的“模块”新特性尚未完全支持。编写或使用自己的“模块”通常没有问题,但是,导入标准库头文件(如下语句)还不能使用,编译会出错。

解决这个问题的方法,可以向项目中添加一个单独的头文件(比如叫“HeaderUnits.h”,如下图),

在这个头文件中预先导入本项目中需要导入的所有标准库头文件,比如 HeaderUnits.h 文件内容如下:

1
2
3
4
5
#pragma once
import <iostream>;
import <vector>;
import <optional>;
import <utility>;

接下来,在“解决方案资源管理器”中右击 HeaderUnits.h,选择下拉菜单的“属性”。然后在“属性”窗口中选择“配置属性 | 常规”,设置“项类型”为“C/C++编译器”(如下图),然后点击“应用”按钮。

下一步,选择“配置属性 | C/C++ | 高级”,将“编译为”设置为“作为 C++ 标头单元编译”(如下图),然后点击“确定”按钮。(这一步不知道怎一设置就报错)

源地址

visual Studio之如何快速的进行注释

注释:选定要注释的区域:ctrl+K,然后再ctrl+C。

解注释:选定要注释的区域:ctrl+K,然后再ctrl+U。