福田做棋牌网站建设,搜索指数查询,工作流程管理系统说明书,青浦区网站建设视频链接#xff1a;【62】【Cherno C】【中字】C的线程_哔哩哔哩_bilibili
参考文章#xff1a;TheChernoCppTutorial_the cherno-CSDN博客
Cherno的C教学视频笔记#xff08;已完结#xff09; - 知乎 (zhihu.com)
C 的线程
#includeiostream
#includeth…视频链接【62】【Cherno C】【中字】C的线程_哔哩哔哩_bilibili
参考文章TheChernoCppTutorial_the cherno-CSDN博客
Cherno的C教学视频笔记已完结 - 知乎 (zhihu.com)
C 的线程
#includeiostream
#includethread
static bool is_Finished false;
void DoWork()
{using namespace std::literals::chrono_literals; // 为 1s 提供作用域std::cout Started thread ID: std::this_thread::get_id()std::endl;while (!is_Finished){std::coutWorking...std::endl;std::this_thread::sleep_for(1s);//等待1s}
}
int main()
{std::thread worker(DoWork);std::cin.get(); // 其作用是阻塞主线程is_Finished true;// 让worker线程终止的条件worker.join();// 让主线程等待worker线程std::cout Finished thread ID: std::this_thread::get_id() std::endl;std::cin.get();
}
C 计时器
1、有两种选择一种是用平台特定的API另一种是用std::chrono此处推荐后者
2、一个比较好的方法是建立一个Timer类在其构造函数里面记下开始时刻在其析构函数里面记下结束时刻并打印从构造到析构所用的时间。如此就可以用这样一个类来对一个作用域进行计时
#includeiostream
#includechrono
struct Timer
{std::chrono::time_pointstd::chrono::steady_clock start,end;std::chrono::durationfloat 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;i100;i)std::coutHellostd::endl;
}
int main()
{Function();std::cin.get();
}
多维数组
int** a2d new int* [50];
for (int i 0; i 50; i)
{a2d[i] new int[50];
}for (int i 0; i 50; i)
{delete[] a2d[i];
}
delete[] a2d;
存储二维数组一个很好的优化方法就是存储在一维数组里面
int** a2d new int* [5];
for (int i 0; i 5; i)
{a2d[i] new int[5];for (int j 0; j 5; j){a2d[i][j] 2;}
}
int* a1d new int[5 * 5];for (int i 0; i 5; i)
{for (int j 0; j 5; j){a1d[i j * 5] 2;}
}
Sorting
此处主要介绍std::sort并结合lambda表达式可进行很灵活的排序
#includeiostream
#includevector
#includealgorithm
int main()
{std::vectorint values {2,3,4,1,5};std::sort(values.begin(), values.end(), [](int a, int b){// 此处两个判断可以将等于2的值放到末尾if(a 2)return false;if(b 2)return true;return a b;});// 此处输出为 13452for(const int v:values)std::coutvstd::endl;std::cin.get();
}
类型双关 type punning
取地址换成对应类型的指针再解引用
#include iostreamint main()
{int a 50;double value *(double*)a;std::cout value std::endl;std::cin.get();
}
1、可以将同一块内存的东西通过不同type的指针给取出来
2、指针的类型只是决定了其1或者-1时地址的偏移量
3、以下这个示例说明了弄清楚内存分布的重要性
struct Entity
{int x,y;
};
int main()
{Entity e {2,3};int* pos (int*)e;std::coutpos[0],pos[1]std::endl;int y *(int*)((char*)e4);std::cout y std::endl;std::cin.get();
} C Union
如果想要以不同形式去取出同一块内存的东西可以用type punning也可以使用union
共用内存。你可以像使用结构体或者类一样使用它们你也可以给它添加静态函数或者普通函数、方法等待。然而你不能使用虚方法还有其他一些限制。但通常人们用联合体来做的事情是和类型双关紧密相关的。
通常union是匿名使用的但是匿名union不能含有成员函数。
#includeiostream
#includevector
#includealgorithm
struct vec2
{float x,y;
};
struct vec4
{union{struct{float x,y,z,w;};struct{vec2 a,b;};};
};
void PrintVec2(const vec2 vec)
{std::coutvec.x,vec.ystd::endl;
}
int main()
{vec4 vector {1.0f,2.0f,3.0f,4.0f};PrintVec2(vector.a); // 输出 12PrintVec2(vector.b); // 输出 34vector.z 10.0f;PrintVec2(vector.a); // 输出 12PrintVec2(vector.b); // 输出 104std::cin.get();
}
虚析构函数
只要你允许一个类拥有子类就一定要把析构函数写成虚函数否则没人能安全地扩展这个类。
C 类型转换
类型转换 casting type casting
C是强类型语言意味着存在一个类型系统并且类型是强制的。 示例
double value 5.25;// C风格的转换
double a (int)value 5.3;// C风格的转换
double s static_castint(value) 5.3;
2、C的cast
static_cast:基础的类型转换结合隐式转换和用户自定义的转换来进行类型转换
dynamic_cast:安全地在继承体系里面向上、向下或横向转换指针和引用的类型多态转换
reinterpret_cast通过解释底层位模式来进行类型转换
const_cast添加或者移除const性质
条件断点和断点操作
1、条件断点当达到什么条件触发断点断点操作当触发断点后执行什么操作在窗口输出什么
2、一个示例在一个死循环里面x每次加一当x被5整除时触发断点触发断点后打出x的值并且可以在调试过程中随时更改断点的条件和动作并且可以设置是否让程序继续运行 现代C中的安全以及如何教授
C里说的安全是什么意思
安全编程或者说是在编程中我们希望降低崩溃、内存泄漏、非法访问等问题。
这一节重点讲讲指针和内存。 用于生产环境使用智能指针用于学习和了解工作积累使用原始指针当然如果你需要定制的话也可以使用自己写的智能指针 Precompiled Headers 预编译头文件
1、由于每次编译时都需要对头文件以及头文件里面包含的头文件进行编译所以编译时间会很长。而预编译头文件则是将头文件预先编译为二进制文件如果此后不修改的话在编译工程的时候就直接用编译好的二进制文件会大大缩短编译时间。
2、只把那些不太经常会被修改的头文件进行预编译如std如windows API或者一些其他的库如GLFW。
3、如果进行预编译头文件一个例子
新建一个工程和解决方案添加Main.cpp,pch.cpp,pch.h三个文件内容分别如下
// Main.cpp
#includepch.hint main()
{std::coutHello!std::endl;std::cin.get();
}
// pch.cpp
#includepch.h
// pch.h
#pragma once
#includeiostream
#includevector
#includememory
#includestring
#includethread
#includechrono
#includeunordered_map
#includeWindows.h
在pch.cpp右键属性-配置属性-C/C-预编译头-预编译头里面选择创建, 并在下一行预编译头文件里面添加 pch.h
在项目名称上右键属性-配置属性-C/C-预编译头-预编译头里面选择使用并在下一行预编译头文件里面添加 pch.h
打开计时工具工具-选项-项目和解决方案-VC项目设置-生成计时就可以看到每次编译的时间
进行对比
进行预编译头文件前后的首次编译耗时分别为2634ms和1745ms
进行预编译头文件前后的二次编译即修改Main.cpp内容后的耗时分别为1235ms和312ms
可以看到进行预编译头文件后时间大大降低
Dynamic Casting
dynamic_cast可以在继承体系里面向上、向下或者平级进行类型转换自动判断类型如果转换失败会返回NULL使用时需要保证是多态即基类里面含有虚函数。由于dynamic_cast使用了RTTI运行时类型识别所以会对性能增加负担
#includeiostream
class Base
{
public:virtual void print(){}
};
class Player : public Base
{
};
class Enemy : public Base
{
};
int main()
{Player* player new Player();Base* base new Base();Base* actualEnemy new Enemy();Base* actualPlayer new Player();// 旧式转换Base* pb1 player; // 从下往上是隐式转换安全Player* bp1 (Player*)base; // 从上往下可以用显式转换危险Enemy* pe1 (Enemy*)player; // 平级转换可以用显式转换危险// dynamic_castBase* pb2 dynamic_castBase*(player); // 从下往上成功转换Player* bp2 dynamic_castPlayer*(base); // 从上往下返回NULLif(bp2) { } // 可以判断是否转换成功Enemy* pe2 dynamic_castEnemy*(player); // 平级转换返回NULLPlayer* aep dynamic_castPlayer*(actualEnemy); // 平级转换返回NULLPlayer* app dynamic_castPlayer*(actualPlayer); // 虽然是从上往下
//但是实际对象是player所以成功转换
}
C中的Structured Binding
C17引入的新特性可以在将函数返回为tuple、pair、struct等结构时且赋值给另外变量的时候直接得到成员而不是结构。确保在项目属性-C/C-语言-C语言标准里面打开C17
#includeiostream
#includetuple
#includestring
// 此处tuple换成pair或者struct结构也是一样的
std::tuplestd::string, int CreatePerson()
{return {ydc,24};
}
int main()
{auto[name,age] CreatePerson();std::coutname,agestd::endl;std::cin.get();
}
std::optional
比如在读取文件内容的时候往往需要判断读取是否成功常用的方法是传入一个引用变量或者判断返回的std::string是否为空例如 C17引入了一个更好的方法std::optional就如名字一样是检测变量是否是present的
#includeiostream
#includefstream
#includeoptional
std::optionalstd::string ReadFileAsString(const std::string filepath)
{std::ifstream stream(filepath);if (stream){std::string result;//read filestream.close();return result;}return {};
}
int main()
{std::optionalstd::string data ReadFileAsString(data.txt);// 可以用has_value()来判断是否读取成功if (data.has_value()){std::coutFile read successfully!\n;}else{std::coutFile not found!\n;}// 也可以用value_or()来判断是否读取成功std::string result data.value_or(Not resprent);//如果数据不存在就会返回我们传入的值 Not resprentstd::coutresultstd::endl;std::cin.get();
}
C 一个变量多种类型 std::variant
C17引入一种可以容纳多种类型变量的结构std::variant
#includeiostream
#includevariant
int main()
{std::variantstd::string,int data; // 里面的类型不能重复data ydc;// 索引的第一种方式std::get但是要与上一次赋值类型相同不然会报错std::coutstd::getstd::string(data)std::endl;// 索引的第二种方式std::get_if传入地址返回为指针if (auto value std::get_ifstd::string(data)){std::string v *value;}data 2;std::coutstd::getint(data)std::endl;std::cin.get();
}
std::variant的大小是里面的大小之和与union不一样union的大小是类型的大小最大值
std::any
也是C17引入的可以存储多种类型变量的结构其本质是一个union但是不像std::variant那样需要列出类型
#includeiostream
#includeany
// 此处写一个new的函数是为了断点看主函数里面哪里调用了new来看其堆栈
void* operator new(size_t size)
{return malloc(size);
}
int main()
{std::any data;data 2;data std::string(ydc);std::string s std::any_caststd::string(data);std::coutsstd::endl;std::cin.get();
}
如何让 string 运行更快
一种调试在heap上分配内存的方法自己写一个new的方法然后设置断点或者打出log就可以知道每次分配了多少内存以及分配了几次
#includeiostream
#includestring
static uint32_t s_AllocCount 0;
void* operator new(size_t size)
{s_AllocCount;std::coutAllocing: size bytes\n;return malloc(size);
}
void PrintName(const std::string name)
{std::coutnamestd::endl;
}
int main()
{std::string fullName yang dingchao;std::string firstName fullName.substr(0,4);std::string lastName fullName.substr(5,8);PrintName(firstName);PrintName(lastName);std::couts_AllocCount allocations\n;std::cin.get();
}
以下为运行结果
Allocing: 8 bytes
Allocing: 8 bytes
Allocing: 8 bytes
yang
dingchao
3 allocations
这个程序仅仅是从一个string取子字符串就多分配了两次内存下面来改进它
2、用C17引入的std::string_view来对同一块内存的string进行截取
#includeiostream
#includestring
static uint32_t s_AllocCount 0;
void* operator new(size_t size)
{s_AllocCount;std::coutAllocing: size bytes\n;return malloc(size);
}
void PrintName(std::string_view name)
{std::coutnamestd::endl;
}
int main()
{std::string fullName yang dingchao;std::string_view firstName(fullName.c_str(),4);std::string_view lastName(fullName.c_str()5,8);PrintName(firstName);PrintName(lastName);std::couts_AllocCount allocations\n;std::cin.get();
}
输出如下
Allocing: 8 bytes
yang
dingchao
1 allocations
3、上面的程序还是有一次分配如果把std::string改成const char*就变成了0次分配
#includeiostream
#includestring
static uint32_t s_AllocCount 0;
void* operator new(size_t size)
{s_AllocCount;std::coutAllocing: size bytes\n;return malloc(size);
}
void PrintName(std::string_view name)
{std::coutnamestd::endl;
}
int main()
{const char* fullName yang dingchao;std::string_view firstName(fullName,4);std::string_view lastName(fullName5,8);PrintName(firstName);PrintName(lastName);std::couts_AllocCount allocations\n;std::cin.get();
}
输出如下
yang
dingchao
0 allocations
Singleton单例
Singleton只允许被实例化一次用于组织一系列全局的函数或者变量与namespace很像。例子随机数产生的类、渲染器类。
#includeiostream
class Singleton
{
public:Singleton(const Singleton) delete; // 删除拷贝复制函数static Singleton Get() // 通过Get函数来获取唯一的一个实例//其定义为static也是为了能直接用类名调用{return s_Instance;}void Function(){} // 执行功能的函数
private:Singleton(){} // 不能让别人实例化所以要把构造函数放进privatestatic Singleton s_Instance; // 定义为static让其唯一
};
Singleton Singleton::s_Instance; // 唯一的实例化的地方
int main()
{Singleton::Get().Function();
}
具体的一个简单的随机数类的例子
#includeiostream
class Random
{
public:Random(const Random) delete; // 删除拷贝复制函数static Random Get() // 通过Get函数来获取唯一的一个实例{static Random instance; // 在此处实例化一次return instance;}static float Float(){ return Get().IFloat();} // 调用内部函数,可用类名调用
private:float IFloat() { return m_RandomGenerator; } // 将函数的实现放进privateRandom(){} // 不能让别人实例化所以要把构造函数放进privatefloat m_RandomGenerator 0.5f;
};
// 与namespace很像
namespace RandomClass {static float s_RandomGenerator 0.5f;static float Float(){return s_RandomGenerator;}
}
int main()
{float randomNum Random::Float();std::coutrandomNumstd::endl;std::cin.get();
}
使用小的string
在release模式下面使用size小于16的string不会分配内存而大于等于16的string则会分配32bytes内存以及更多所以16个字符是一个分界线
#includeiostream
void* operator new(size_t size)
{std::coutAllocated: size bytes\n;return malloc(size);
}
int main()
{std::string longName ydc ydc ydc ydc ydc;std::string shortName ydc;std::cin.get();
}
Release模式只有longName在heap上面分配内存了输出如下
Allocated: 32 bytes
跟踪内存分配的简易办法
重写new和delete操作符函数并在里面打印分配和释放了多少内存也可在重载的这两个函数里面设置断点通过查看调用栈即可知道什么地方分配或者释放了内存
#includeiostream
void* operator new(size_t size)
{std::coutAllocing size bytes\n;return malloc(size);
}
void operator delete(void* memory, size_t size)
{std::coutFree size bytes\n;free(memory);
}
struct Entity
{int x,y,z;
};
int main()
{{std::string name ydc;}Entity* e new Entity();delete e;std::cin.get();
}
还可以写一个简单统计内存分配的类在每次new的时候统计分配内存在每次delete时统计释放内存可计算出已经分配的总内存
#includeiostream
struct AllocationMertics
{uint32_t TotalAllocated 0;uint32_t TotalFreed 0;uint32_t CurrentUsage() {return TotalAllocated - TotalFreed;}
};
static AllocationMertics s_AllocationMetrics;
void* operator new(size_t size)
{s_AllocationMetrics.TotalAllocatedsize;return malloc(size);
}
void operator delete(void* memory, size_t size)
{s_AllocationMetrics.TotalFreed size;free(memory);
}
static void PrintMemoryUsage()
{std::coutMemory usage: s_AllocationMetrics.CurrentUsage() bytes\n;
}
int main()
{PrintMemoryUsage();{std::string name ydc;PrintMemoryUsage();}PrintMemoryUsage();std::cin.get();
} lvalue and rvalue左值和右值
1、 左值有存储空间的值往往长期存在右值没有存储空间的短暂存在的值
2、 一般而言赋值符号左边的是左值右边的是右值
3、在给函数形参列表传参时有四种情况
#includeiostream
void PrintName(std::string name) // 可接受左值和右值
{std::coutnamestd::endl;
}
void PrintName(std::string name) // 只接受左值引用不接受右值
{std::cout name std::endl;
}
void PrintName(const std::string name) // 接受左值和右值把右值当作const lvalue
{std::cout name std::endl;
}
void PrintName(std::string name) // 接受右值引用
{std::cout name std::endl;
}
int main()
{std::string firstName yang;std::string lastName dingchao;std::string fullName firstName lastName;PrintName(fullName);PrintName(firstNamelastName);std::cin.get();
}
move semantics
比如一个类Entity含有一个成员Name为String类型如果要用常量字符串来初始化这个类就会先调用String的构造函数再调用String的拷贝构造函数经Entity构造函数里面调用然后再调用String的析构函数但是使用move操作就可以让中间的一次拷贝变成move就可以少一次new我理解为浅拷贝的意思
#includeiostream
class String
{
public:String() default;String(const char* string) //构造函数{printf(Created\n);m_Size strlen(string);m_Data new char[m_Size];memcpy(m_Data,string,m_Size);}String(const String other) // 拷贝构造函数{printf(Copied\n);m_Size other.m_Size;m_Data new char[m_Size];memcpy(m_Data,other.m_Data,m_Size);}String(String other) noexcept // 右值引用拷贝相当于移动就是把复制一次指针原来的指针给nullptr{printf(Moved\n);m_Size other.m_Size;m_Data other.m_Data;other.m_Size 0;other.m_Data nullptr;}~String(){printf(Destroyed\n);delete m_Data;}
private:uint32_t m_Size;char* m_Data;
};
class Entity
{
public:Entity(const String name) : m_Name(name){}Entity(String name) : m_Name(std::move(name)) // std::move(name)也可以换成(String)name{}
private:String m_Name;
};
int main()
{ Entity entity(ydc);std::cin.get();
}
如此的代码在实例化entity的时候如果传入的是字符串常量右值则会调用拷贝的右值版本避免了一次new如果传入的是String左值则仍然会进行一次左值拷贝
std::move
1、使用std::move返回一个右值引用可以将本来的copy操作变为move操作
#includeiostreamclass String
{
public:String() default;String(const char* string){printf(Created\n);m_Size strlen(string);m_Data new char[m_Size];memcpy(m_Data,string,m_Size);}String(const String other){printf(Copied\n);m_Size other.m_Size;m_Data new char[m_Size];memcpy(m_Data,other.m_Data,m_Size);}String operator(const String other){printf(Cpoy Assigned\n);delete [] m_Data;m_Size other.m_Size;m_Data new char[m_Size];memcpy(m_Data, other.m_Data, m_Size);return *this;}String(String other) noexcept{printf(Moved\n);m_Size other.m_Size;m_Data other.m_Data;other.m_Size 0;other.m_Data nullptr;}String operator(String other) noexcept{printf(Move Assigned\n);if(this ! other){ delete [] m_Data;m_Size other.m_Size;m_Data other.m_Data;other.m_Size 0;other.m_Data nullptr;}return *this;}~String(){printf(Destroyed\n);delete m_Data;}
private:uint32_t m_Size;char* m_Data;
};
int main()
{ String name ydc; // String name(ydc);调用构造函数String nameCopy name; // String nameCopy(name);调用拷贝构造函数String nameAssign;nameAssign name; // 调用拷贝赋值函数String nameMove std::move(name); // String nameMove(std::move(name));调用右值引用构造函数String nameMoveAssign;nameMoveAssign std::move(name); // 调用右值引用赋值函数std::cin.get();
}