ChernoCPP 2

视频链接:【62】【Cherno C++】【中字】C++的线程_哔哩哔哩_bilibili

参考文章:TheChernoCppTutorial_the cherno-CSDN博客

Cherno的C++教学视频笔记(已完结) - 知乎 (zhihu.com)

C++ 的线程

#include<iostream>
#include<thread>
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::cout<<"Working..."<<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类,在其构造函数里面记下开始时刻,在其析构函数里面记下结束时刻,并打印从构造到析构所用的时间。如此就可以用这样一个类来对一个作用域进行计时:

#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"<<std::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表达式可进行很灵活的排序:

#include<iostream>
#include<vector>
#include<algorithm>
int main()
{std::vector<int> 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;});// 此处输出为 1,3,4,5,2for(const int &v:values)std::cout<<v<<std::endl;std::cin.get();
}

类型双关 type punning

(取地址,换成对应类型的指针,再解引用) 

#include <iostream>int 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::cout<<pos[0]<<","<<pos[1]<<std::endl;int y = *(int*)((char*)&e+4);std::cout << y << std::endl;std::cin.get();
}

C++ Union

如果想要以不同形式去取出同一块内存的东西,可以用type punning,也可以使用union

共用内存。你可以像使用结构体或者类一样使用它们,你也可以给它添加静态函数或者普通函数、方法等待。然而你不能使用虚方法,还有其他一些限制。但通常人们用联合体来做的事情,是和类型双关紧密相关的。

通常union是匿名使用的,但是匿名union不能含有成员函数。

#include<iostream>
#include<vector>
#include<algorithm>
struct vec2
{float x,y;
};
struct vec4
{union{struct{float x,y,z,w;};struct{vec2 a,b;};};
};
void PrintVec2(const vec2& vec)
{std::cout<<vec.x<<","<<vec.y<<std::endl;
}
int main()
{vec4 vector = {1.0f,2.0f,3.0f,4.0f};PrintVec2(vector.a); // 输出 1,2PrintVec2(vector.b); // 输出 3,4vector.z = 10.0f;PrintVec2(vector.a); // 输出 1,2PrintVec2(vector.b); // 输出 10,4std::cin.get();
}

虚析构函数

只要你允许一个类拥有子类,就一定要把析构函数写成虚函数,否则没人能安全地扩展这个类。

C++ 类型转换

类型转换 casting, type casting

C++是强类型语言,意味着存在一个类型系统并且类型是强制的。
示例:

double value = 5.25;// C风格的转换
double a = (int)value + 5.3;// C++风格的转换
double s = static_cast<int>(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
#include"pch.h"int main()
{std::cout<<"Hello!"<<std::endl;std::cin.get();
}
// pch.cpp
#include"pch.h"
// pch.h
#pragma once
#include<iostream>
#include<vector>
#include<memory>
#include<string>
#include<thread>
#include<chrono>
#include<unordered_map>
#include<Windows.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(运行时类型识别),所以会对性能增加负担

#include<iostream>
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_cast<Base*>(player); // 从下往上,成功转换Player* bp2 = dynamic_cast<Player*>(base); // 从上往下,返回NULLif(bp2) { } // 可以判断是否转换成功Enemy* pe2 = dynamic_cast<Enemy*>(player); // 平级转换,返回NULLPlayer* aep = dynamic_cast<Player*>(actualEnemy); // 平级转换,返回NULLPlayer* app = dynamic_cast<Player*>(actualPlayer); // 虽然是从上往下,
//但是实际对象是player,所以成功转换
}

C++中的Structured Binding

C++17引入的新特性,可以在将函数返回为tuple、pair、struct等结构时且赋值给另外变量的时候,直接得到成员,而不是结构。(确保在项目属性-C/C++-语言-C++语言标准,里面打开C++17)

#include<iostream>
#include<tuple>
#include<string>
// 此处tuple换成pair或者struct结构也是一样的
std::tuple<std::string, int> CreatePerson()
{return {"ydc",24};
}
int main()
{auto[name,age] = CreatePerson();std::cout<<name<<","<<age<<std::endl;std::cin.get();
}

std::optional

比如在读取文件内容的时候,往往需要判断读取是否成功,常用的方法是传入一个引用变量或者判断返回的std::string是否为空,例如:

C++17引入了一个更好的方法,std::optional,就如名字一样,是检测变量是否是present的:

#include<iostream>
#include<fstream>
#include<optional>
std::optional<std::string> ReadFileAsString(const std::string& filepath)
{std::ifstream stream(filepath);if (stream){std::string result;//read filestream.close();return result;}return {};
}
int main()
{std::optional<std::string> data = ReadFileAsString("data.txt");// 可以用has_value()来判断是否读取成功if (data.has_value()){std::cout<<"File read successfully!\n";}else{std::cout<<"File not found!\n";}// 也可以用value_or()来判断是否读取成功std::string result = data.value_or("Not resprent");//如果数据不存在,就会返回我们传入的值 Not resprentstd::cout<<result<<std::endl;std::cin.get();
}

C++ 一个变量多种类型  std::variant

C++17引入一种可以容纳多种类型变量的结构,std::variant

#include<iostream>
#include<variant>
int main()
{std::variant<std::string,int> data; // <>里面的类型不能重复data = "ydc";// 索引的第一种方式:std::get,但是要与上一次赋值类型相同,不然会报错std::cout<<std::get<std::string>(data)<<std::endl;// 索引的第二种方式,std::get_if,传入地址,返回为指针if (auto value = std::get_if<std::string>(&data)){std::string& v = *value;}data = 2;std::cout<<std::get<int>(data)<<std::endl;std::cin.get();
}

std::variant的大小是<>里面的大小之和,与union不一样,union的大小是类型的大小最大值

std::any

也是C++17引入的可以存储多种类型变量的结构,其本质是一个union,但是不像std::variant那样需要列出类型

#include<iostream>
#include<any>
// 此处写一个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_cast<std::string&>(data);std::cout<<s<<std::endl;std::cin.get();
}

如何让 string 运行更快

一种调试在heap上分配内存的方法,自己写一个new的方法,然后设置断点或者打出log,就可以知道每次分配了多少内存,以及分配了几次:

#include<iostream>
#include<string>
static uint32_t s_AllocCount = 0;
void* operator new(size_t size)
{s_AllocCount++;std::cout<<"Allocing: "<<size<<" bytes\n";return malloc(size);
}
void PrintName(const std::string& name)
{std::cout<<name<<std::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::cout<<s_AllocCount<<" allocations\n";std::cin.get();
}

以下为运行结果:

Allocing: 8 bytes
Allocing: 8 bytes
Allocing: 8 bytes
yang
dingchao
3 allocations

这个程序仅仅是从一个string取子字符串,就多分配了两次内存,下面来改进它

2、用C++17引入的std::string_view来对同一块内存的string进行截取

#include<iostream>
#include<string>
static uint32_t s_AllocCount = 0;
void* operator new(size_t size)
{s_AllocCount++;std::cout<<"Allocing: "<<size<<" bytes\n";return malloc(size);
}
void PrintName(std::string_view name)
{std::cout<<name<<std::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::cout<<s_AllocCount<<" allocations\n";std::cin.get();
}

输出如下:

Allocing: 8 bytes
yang
dingchao
1 allocations

3、上面的程序还是有一次分配,如果把std::string改成const char*,就变成了0次分配:

#include<iostream>
#include<string>
static uint32_t s_AllocCount = 0;
void* operator new(size_t size)
{s_AllocCount++;std::cout<<"Allocing: "<<size<<" bytes\n";return malloc(size);
}
void PrintName(std::string_view name)
{std::cout<<name<<std::endl;
}
int main()
{const char* fullName = "yang dingchao";std::string_view firstName(fullName,4);std::string_view lastName(fullName+5,8);PrintName(firstName);PrintName(lastName);std::cout<<s_AllocCount<<" allocations\n";std::cin.get();
}

输出如下:

yang
dingchao
0 allocations

Singleton单例

Singleton只允许被实例化一次,用于组织一系列全局的函数或者变量,与namespace很像。例子:随机数产生的类、渲染器类。

#include<iostream>
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();
}

具体的一个简单的随机数类的例子:

#include<iostream>
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::cout<<randomNum<<std::endl;std::cin.get();
}

使用小的string

在release模式下面,使用size小于16的string,不会分配内存,而大于等于16的string,则会分配32bytes内存以及更多,所以16个字符是一个分界线

#include<iostream>
void* operator new(size_t size)
{std::cout<<"Allocated: "<<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操作符函数,并在里面打印分配和释放了多少内存,也可在重载的这两个函数里面设置断点,通过查看调用栈即可知道什么地方分配或者释放了内存

#include<iostream>
void* operator new(size_t size)
{std::cout<<"Allocing "<<size<<" bytes\n";return malloc(size);
}
void operator delete(void* memory, size_t size)
{std::cout<<"Free "<<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时统计释放内存,可计算出已经分配的总内存:

#include<iostream>
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.TotalAllocated+=size;return malloc(size);
}
void operator delete(void* memory, size_t size)
{s_AllocationMetrics.TotalFreed += size;free(memory);
}
static void PrintMemoryUsage()
{std::cout<<"Memory usage: "<<s_AllocationMetrics.CurrentUsage()<<" bytes\n";
}
int main()
{PrintMemoryUsage();{std::string name = "ydc";PrintMemoryUsage();}PrintMemoryUsage();std::cin.get();
}

 lvalue and rvalue(左值和右值)

1、 左值:有存储空间的值,往往长期存在;右值:没有存储空间的短暂存在的值

2、 一般而言,赋值符号=左边的是左值,右边的是右值

3、在给函数形参列表传参时,有四种情况:

#include<iostream>
void PrintName(std::string name) // 可接受左值和右值
{std::cout<<name<<std::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(firstName+lastName);std::cin.get();
}

 int& a = 10;  报错

 const int& a = 10;  可通过  编译器可能会用你的存储创建一个临时变量,然后把它赋值给那  个引用

move semantics

比如一个类Entity含有一个成员Name为String类型,如果要用常量字符串来初始化这个类,就会先调用String的构造函数,再调用String的拷贝构造函数(经Entity构造函数里面调用),然后再调用String的析构函数,但是使用move操作就可以让中间的一次拷贝变成move,就可以少一次new,我理解为浅拷贝的意思:

#include<iostream>
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操作:

#include<iostream>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& 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));调用右值引用构造函数
//用 std::move(name) 将name转换成临时变量String nameMoveAssign;nameMoveAssign = std::move(name); // 调用右值引用赋值函数std::cin.get();
}

输出:

Created
Copied
Cpoy Assigned
Moved
Move Assigned

自己实现一个 Array 类

#include<iostream>
template<typename T,size_t S>
class Array
{
public:constexpr int Size() const {return S;} 
// const放在成员函数后面,表示函数不能修改值;用constexpr来修饰表示返回值是常量字面值,
//可以被编译器优化T& operator[](size_t index) {return m_Data[index]; } // 返回引用以对原数据进行修改const T& operator[](size_t index) const {return m_Data[index]; }T* Data(){return m_Data;} // 返回数组本身,实际上是个指针,其地址等价于&m_Data[0]const T* Data() const {return m_Data;}
private:T m_Data[S];
};
int main()
{	Array<int,5> data;memset(&data[0],0,data.Size()*sizeof(int));data[2] = 2;for(size_t i = 0;i<data.Size();i++)std::cout<<data[i]<<std::endl;std::cin.get();
}

 constexpr 变量和 constexpr 函数

参考链接:C++11新特性:constexpr变量和constexpr函数 - Rser_ljw - 博客园 (cnblogs.com)

常量表达式指不会改变并且在编译过程中就能得到计算结果的值

const int max_files = 20;   //是常量表达式

const int sz = get_size();  //不是常量表达式,运行时才能直到结果

允许将变量声明为 constexpr 类型以便由编译器验证变量的值是否是一个常量表达式。如果不是,编译器报错。同时,声明为 constexpr 的变量一定是长岭,而且必须用常量表达式初始化

constexpr int mf  =20;  //正确

constexpr int limit = size(); //未知,若 size() 函数是一个 constexpr 函数就正确,反之错误

int i = 10;

constexpr int t = i; // 错误,i 不是常量

字面值类型

声明 constexpr 变量时用到的类型被称为字面值类型。算术类型,引用,指针,枚举和一些特输的类都属于字面值类型,而IO库,string类型则不属于字面值类型,也就不能被定义为 constexpr

字面值常量

常量是指 const 声明或定义一个变量,使之成为常量。如 const int buffSize = 10; buffSize 在程序中不允许被修改,是常量。而字面值常量是指只能用它的值来称呼,不能被修改的值,如 4.234,0x23,“sdjskd”

指针 与 constexpr

​ 对于指针而言,constexpr仅对指针本身有效与指针所指对象无关

const int *p = nullptr;            //正确,p是一个指向整型常量的指针
constexpr int *q = nullptr;        //正确,但q是一个指向  整数  的  常量指针

 constexpr指针既可以指向常量也可以指向一个非常量

constexpr 函数

指能用于常量表达式的函数,该函数要遵循规定:函数的返回类型以及所有形参的类型都得是字面值类型(声明 constexpr 变量时用到的类型),并且函数体中必须只有一条 return 语句

constexpr int new_sz() { return 42; }//constexpr函数
constexpr int foo = new_sz();
//在对变量foo初始化时,编译器把对constexpr函数的调用替换成其结果值。
//为了能在编译过程中随时展开,constexpr函数被隐式地指定为内联函数。

每当需要 const 整数时(如在模板自变量和数组声明中),都可以使用 constexpr 整数值。 如果在编译时(而非运行时)计算某个值,它可以使程序运行速度更快、占用内存更少

C++11系列-常量表达式 - 书写|记下人生痕迹 (towriting.com)

常量表达式主要是允许一些计算发生在编译时,即发生在代码编译而不是运行的时候。这是很大的优化:假如有些事情可以在编译时做,它将只做一次,而不是每次程序运行时。需要计算一个编译时已知的常量,比如特定值的sine或cosin?确实你亦可以使用库函数sin或cos,但那样你必须花费运行时的开销。使用constexpr,你可以创建一个编译时的函数,它将为你计算出你需要的数值。用户的电脑将不需要做这些工作。

为了使函数获取编译时计算的能力,你必须指定constexpr关键字到这个函数。 

constexpr int multiply (int x, int y)
{return x * y;
}// 将在编译时计算
const int val = multiply( 10, 10 );

除了编译时计算的性能优化,constexpr的另外一个优势是,它允许函数被应用在以前调用宏的所有场合。例如,你想要一个计算数组size的函数,size是10的倍数。如果不用constexpr,你需要创建一个宏或者使用模板,因为你不能用函数的返回值去声明数组的大小。但是用constexpr,你就可以调用一个constexpr函数去声明一个数组。

constexpr int getDefaultArraySize (int multiplier)
{return 10 * multiplier;
}int my_array[ getDefaultArraySize( 3 ) ];

注意递归并不受限制。但只允许一个返回语句,那如何实现递归呢?可以使用三元运算符(?:)。例如,计算n的阶乘:

constexpr int factorial (int n)
{return n > 0 ? n * factorial( n - 1 ) : 1;
}

constexpr函数还有那些特点?

一个constexpr函数,只允许包含一行可执行代码。但允许包含typedefs、 using declaration && directives、静态断言等。

一个声明为constexpr的函数同样可以在运行时被调用,当这个函数的参数是非常量的,这意味着你不需要分别写运行时和编译时的函数。

编译时使用对象

假如你有一个Circle类:

class Circle
{public:Circle (int x, int y, int radius) : _x( x ), _y( y ), _radius( radius ) {}double getArea () const{return _radius * _radius * 3.1415926;}private:int _x;int _y;int _radius;
};

你希望在编译期构造一个Circle接着算出他的面积。

constexpr Circle c( 0, 0, 10 );
constexpr double area = c.getArea();

事实证明你可以给Circle类做一些小的修改以完成这件事。首先,我们需要将构造函数声明为constexpr,接着我们需要将getarea函数声明为constexpr。将构造函数声明为constexpr则运行构造函数在编译期运行,只要这个构造函数的参数为常量,且构造函数仅仅包含成员变量的constexpr构造(所以默认构造可以看成constexpr,只要成员变量都有constexpr构造)。

class Circle
{public:constexpr Circle (int x, int y, int radius) : _x( x ), _y( y ), _radius( radius ) {}constexpr double getArea () {return _radius * _radius * 3.1415926;}private:int _x;int _y;int _radius;
};

constexpr vs const

假如你将一个成员函数标记为constexpr,则顺带也将它标记为了const。如果你将一个变量标记为constexpr,则同样它是const的。但相反并不成立,一个const的变量或函数,并不是constexpr的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/301784.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【攻防世界】catcat-new

首先进入环境&#xff0c;这是一个介绍猫的网站&#xff1a; 网站的URL没有发现问题&#xff0c;使用dirsearch对网站进行扫描&#xff0c;看是否有可以访问的窗口&#xff1a; 发现 /admin 可以访问&#xff0c;我们尝试访问&#xff1a; /admin中没有flag。我们返回初始界面&…

Redis数据库的简介、部署及常用命令

关系数据库与非关系型数据 关系型数据库 关系型数据库是一个结构化的数据库&#xff0c;创建在关系模型&#xff08;二维表格模型&#xff09;基础上&#xff0c;一般面向于记录。sQL语句&#xff08;标准数据查询语言&#xff09;就是一种基于关系型数据库的语言&#xff0c…

LangChain - OpenGPTs

文章目录 MessageGraph 消息图认知架构AssistantsRAGChatBot 持久化配置新模型新工具astream_events总结 关键链接&#xff1a; OpenGPT GitHub 存储库YouTube 上的 OpenGPT 演练LangGraph&#xff1a;Python、JS 两个多月前&#xff0c;在 OpenAI 开发日之后&#xff0c;我们…

项目管理中的估算活动资源

在项目管理中&#xff0c;资源估算是一项至关重要的任务。正确地估算活动资源可以确保项目的顺利进行&#xff0c;避免资源浪费和不必要的延误。以下是对项目管理中常见的活动资源类型的详细分析。 一、人力资源 人力资源是项目管理中最基本的资源之一。它包括项目团队成员的…

Linux gcc day5粘滞位

粘滞位 背景&#xff1a;一定时在一个公共目录&#xff08;root创建&#xff09;下。进行临时文件的操作 Linux系统中有很多人&#xff0c;我们需要在一个公共目录下&#xff0c;进行临时文件的操作&#xff08;增删查改&#xff09; 创建一个根目录下的dir&#xff08;mytmp…

指针的深入理解(六)

指针的深入理解&#xff08;六&#xff09; 个人主页&#xff1a;大白的编程日记 感谢遇见&#xff0c;我们一起学习进步&#xff01; 文章目录 指针的深入理解&#xff08;六&#xff09;前言一. sizeof和strlen1.1sizeof1.2strlen1.3sizeof和strlen对比 二.数组名和指针加减…

Java变量详解

​ 这里写目录标题 第一章、Java中的变量分类1.1&#xff09;变量分类1.2&#xff09;成员变量分类1.3&#xff09;成员变量和局部变量的区别 第二章、成员变量详解2.1&#xff09;成员变量作用域/权限修饰符2.2&#xff09;成员变量和成员属性的区别2.3&#xff09;成员变量初…

是否有替代U盘,可安全交换的医院文件摆渡方案?

医院内部网络存储着大量的敏感医疗数据&#xff0c;包括患者的个人信息、病历记录、诊断结果等。网络隔离可以有效防止未经授权的访问和数据泄露&#xff0c;确保这些敏感信息的安全。随着法律法规的不断完善&#xff0c;如《网络安全法》、《个人信息保护法》等&#xff0c;医…

spring事务那些事

实际工作中还会面临千奇百怪的问题&#xff0c;看下面返个例子&#xff08;注意MySql数据库测试&#xff09;&#xff1a; //1.hello1Service 调用 hello2Service Transactional(propagation Propagation.REQUIRED,rollbackFor Exception.class) public void doUpdate() {//…

【CHI】(十二)Memory Tagging

目录 1. Introduction 2. Message extensions 3. Tag coherency 4. Read transaction rules 4.1 TagOp values 4.2 Permitted initial MTE tag states 5. Write transactions 5.1 Permitted TagOp values 5.2 TagOp, TU, and tags relationship 6. Dataless transact…

Java 处理Mysql获取树形的数据

Mysql数据&#xff1a; 代码如下&#xff1a; Entity&#xff1a; Data Accessors(chain true) public class Region {private BigInteger id;//名称private String name;//父idprivate BigInteger parentId;private List<Region> children;private Integer createTim…

书生·浦语训练营二期第二次笔记

文章目录 1. 部署 InternLM2-Chat-1.8B 模型进行智能对话1.1 配置环境1.2 下载 InternLM2-Chat-1.8B 模型 2. 实战&#xff1a;部署实战营优秀作品 八戒-Chat-1.8B 模型2.1 配置基础环境2.2 使用 git 命令来获得仓库内的 Demo 文件&#xff1a;2.3 下载运行 Chat-八戒 Demo 3. …

Vue2 —— 学习(二)

shsh一、绑定 class 样式 &#xff08;一&#xff09;字符串写法 1.流程介绍 适用于样式的类名不确定 需要动态指定 通过 指令绑定的形式&#xff0c;在模板中的类标签绑定 Vue 实例中的数据 mood <div class"basic" :class"mood" click"chang…

AutoAlignV2:基于可变形特征聚合的动态多模态3D目标检测

AutoAlignV2 Deformable Feature Aggregation for Dynamic Multi-Modal 3D Object Detection 论文网址&#xff1a;AutoAlignV2 论文代码&#xff1a;AutoAlignV2 简读论文 这篇论文提出了一种名为AutoAlignV2的动态多模态3D目标检测框架,旨在高效融合激光雷达点云和RGB图像…

双目运算符和单目运算符的重载

目录 题目 源码 结果示例 题目 建立一个矩阵类&#xff0c;可以完成指定的操作或运算。 说明&#xff1a; 矩阵为2行3列&#xff0c;基类型为整型&#xff1b;操作或运算&#xff1a;初始化&#xff08;>>&#xff09;、输出&#xff08;<<&#xff09;、赋值…

2024年网络安全趋势前瞻:从AI攻击到云安全新挑战

随着2024年开展新的序幕&#xff0c;网络安全领域正面临着前所未有的挑战与机遇&#xff0c;一系列引人注目的趋势和预测逐渐浮出水面。 一、AI技术发展引发的安全问题 近年来&#xff0c;我们见证了AI技术的飞速进步&#xff0c;其中ChatGPT等引领潮流的AI服务成为公众瞩目的…

数据结构__顺序表

概念及结构 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;一般情况下采用数组存储。在数组上完成数据的增删查改 需要用到数组&#xff1a;数组的绝对优势&#xff1a;下标的随机访问&#xff08;因为物理空间连续&#xff09; a[i]等…

git 常用命令和使用方法

作者简介&#xff1a; 一个平凡而乐于分享的小比特&#xff0c;中南民族大学通信工程专业研究生在读&#xff0c;研究方向无线联邦学习 擅长领域&#xff1a;驱动开发&#xff0c;嵌入式软件开发&#xff0c;BSP开发 作者主页&#xff1a;一个平凡而乐于分享的小比特的个人主页…

IoT数采平台4:测试

IoT数采平台1&#xff1a;开篇IoT数采平台2&#xff1a;文档IoT数采平台3&#xff1a;功能IoT数采平台4&#xff1a;测试 Modbus RTU串口测试 OPC测试 HTTP测试 MQTT透传测试 MQTT网关测试及数据上报 TCP / UDP 监听&#xff0c;客户端连上后发送信息&#xff0c;客户端上报数据…

Linux从入门到精通 --- 4(下).网络请求和下载、端口、进程管理、主机状态监控、环境变量、文件的上传和下载、压缩和解压

文章目录 第四章(下)&#xff1a;4.8 网络请求和下载4.8.1 ping4.8.2 wget4.8.3 curl 4.9 端口4.9.1 查看端口占用 4.10 进程管理4.10.1 查看进程4.10.2 查看指定进程4.10.3 关闭进程 4.11 主机状态监控4.11.1 查看系统资源占用4.11.2 top交互式选项4.11.3 磁盘信息监控4.11.4 …