待补充
1.语法区别:仅有内部成员可见性的区别。类成员默认是private,而结构体成员默认是public。
2.常用方式:
1.外部static指位于类或结构体定义的外部。
2.通常用于修饰全局变量、类或结构体的定义。
3.作用:主要用于链接阶段,限制全局变量、类或结构体仅在本编译单元(通常指当前.cc源文件)可见,而在其他编译单元中不可见。
4.使用建议:如果不是明确需要全局可见性,最好加上static修饰。
1.内部static指用于修饰类或结构体中的成员变量或成员函数。
2.作用:通过类或结构体可以定义多个对象,但是用static修饰的成员在内存中仅有一份。
3.使用限制:
1.局部静态变量通常指static修饰函数内定义的局部变量。
2.作用:使局部变量的生存周期扩大为整个程序的生成周期,但是变量的作用域不变。
1.枚举可以在类中定义
2.类构成一个命名空间。但是枚举不是命名空间,可以直接访问枚举成员,而不必加枚举类型名。
1.定义:实例化类对象时,自动被调用的函数。
2.在C++中默认构造函数等于空函数,什么都不做。Java的默认构造函数会将成员变量初始化。
3.阻止类实例化对象的方法:显示定义一个默认构造函数(没有参数),并且仅有这一个构造函数,并且将该构造函数设置为private。
1.定义:在类对象被销毁时,自动被调用的函数。
2.适用范围:
1.在子类中,可以重写父类中的方法。但是相比于不重写父类中的方法,需要占用额外的内存,因为子类需要维护一个虚函数表。
1.定义:使用virtual关键字修饰类中成员函数的定义。通常用于修饰基类中的成员函数,因为基类中的成员函数可能被子类覆写override。
2.作用:编译时会告知编译器生成虚函数表,主要用于实现多态。
3.C++11引入的override关键字:
4.虚函数的开销:
1.纯虚函数:类的成员方法,只声明了函数,没有实现函数体。(通常将函数体{…}替换为 =0;)
2.接口:只包含纯虚函数的类。
1.可见性修饰符:
1.在堆上申请数组内存
int *arr = new int[5];
delete[] arr;
2.C++11引入了std:array,优点:
#include
std::array arr; // 指明数组元素的类型和个数
for (int i = 0; i < arr.size(); i++) {arr[1] = 10;}
3.C++数组与C数组的不同之处
// 该数组定义在C++中合法,但是在C中非法
static const int size = 5;
int arr[size];
#include
#include
// void Print(std::string name) // 类对象作为函数参数时,是值拷贝的。建议使用引用作为类对象的参数
void Print(const std::string& name)
{
std::cout << name <
1.不同的字符类型:
const char *name1 = "Cherno";
const char *name2 = u8"Cherno";
const wchar_t *name3 = L"Cherno"; // wchar_t 1至4字节
const char16_t *name4 = u"Cherno"; // 2字节
const char32_t *name5 = U"Cherno"; // 4字节
2.C++14引入的std::string_literals
using namespace std::string_literals;
std::string name = "hello"s + "world"; // 在C++14中合法,后缀s表示将“hello”转换为std::string_literals类型;在C++11中非法
std::string name = std::string("hello") + "world"; // 在C++14中合法;在C++11中合法
3.忽略字符串中的转移字符
std::string name = R("line1
line2
line3"); // 前缀R表示忽略字符串中的转义字符
1.const修饰符在变量定义时的位置
const int *var = new int; // 不能修改var指针,指向的内存;但是可以修改var的内容,即var可以指向新的内存地址。
int const * var = new int; // 同上
int * const var = new int; // 可以修改var指针,指向的内存;但是不能修改var的内容,即var不能指向新的内存地址。
2.const用于C++类中的成员函数的定义
class Entity {
private:
int m_X, *m_Y;
mutable int var;
public:
int GetX1() const { // 该const表示,在方法定义中不能修改类的成员变量。
// m_X = 2; // 修改m_X会报错。
return m_X;
}
int GetX2() const {
var = 2; // 被mutable修饰的成员变量,可以在const修饰的成员函数中修改。
return m_X;
}
const int * const GetY1() const { // 第一个const:返回的指针变量的内容不能被修改;第2个const:返回的指针变量本身不能被修改。
return m_Y;
}
};
void PrintEntity(const Entity &e) {
std::cout << e.GetX() << std::endl; // 变量e的引用被const修饰,e的成员函数GetX()也必须被const修饰,否则编译不通过。
}
Entity e;
class Example
{
private:
int num;
public:
Example()
{
std::cout << "once" << std::endl;
}
Example(int n)
{
num = n;
std::cout << "twice with " << n << std::endl;
}
}
class Entity
{
private:
std::string m_Name;
int m_Age;
Example m_Example;
public:
Entity(std::string &name, int age)
{
m_Name = name;
m_Age = age;
// 结论:将会打印“once”和“twice with 8”。原因:成员变量m_Example定义时,将调用无参的构造函数。
m_Example = Example(8);
// do other thingsl
}
// 结论:将会打印“twice with 8”。
// 结论2:不仅是代码风格的问题! 成员初始化列表减少了类成员对象的默认构造函数的调用,提高了性能。
Entity() : m_Name("lfc"), m_Age(18), m_Example(Example(8))
{
// do other thingsl
}
}
结论:尽量用成员初始化列表,对性能提升有帮助。
方式1:
int age = 18;
std::string desp; // 调用默认构造函数,创建了desp对象
if (age > 18) {
desp = "man"; // 再次调用std::string的构造函数,并创建了新的对象。
} else {
desp = "child";
}
方式2:
int age = 18;
std::string desp = age > 18 ? "man" : "child"; // 仅调用了一次std::string的构造函数。
// 在堆上分配
Entity *e = new Entity("Cherno");
delete e;
// 在栈上分配
Entity e2 = Entity("Cherno");
Entity e3("Cherno");
// 数组的申请和释放
int *arr = new int[50];
delete[] arr;
// 类对象的申请和释放
Entity *e = new Entity() // 在堆上分配内存,并调用类的构造函数。
delete e; 调用析构函数,并释放堆内存。
class Entity {
private:
std:string m_Name;
int m_Age;
public:
Entity(std::string name) : m_Name(name), m_Age(-1) {}
Entity(std::string name) : m_Name(name), m_Age(-1) {}
// 在定义构造函数时,使用explicit关键字进行了声明。作用:与隐式转换相关,请看本节介绍。
// explicit Entity(std::string name) : m_Name(name), m_Age(-1) {}
};
void PrintEntity(Entity &e) {
// print something
}
Entity e1 = "Cherno"; // 合法。使用了隐式构造函数
Entity e2 = 22; // 合法。使用了隐式构造函数
PrintEntity(22); // 合法。使用了隐式构造函数
PrintEntity("Cherno"); // 不合法。隐式转换只能进行一次。此处期望将const char *类型的"Cherno"转换为std::string,然后再转换为Entity。需要进行
两次转换,所以不合法。
explicit关键字,可以禁用隐式构造函数。
class Vector2 {
private:
float x;
float y;
public:
Vector2(float x, float y) : x(x), y(y) {} // 构造函数
Vector2 Add(const Vector2 & other) { // Add函数
return Vector2(x + other,x, y + other.y);
}
Vector2 operator+(const Vector2 & other) { // 重载操作符“+”
return Vector2(x + other,x, y + other.y);
}
};
Vector2 v1(1.1, 2.2);
Vector2 v2(3.3, 4.4);
Vector2 v3 = v1.Add(&v2); // 调用Add函数,进行加法运算
Vector2 v4 = v1 + v2; // 使用重载的操作符"+",进行加法运算
class Entity {
public:
Entity() {
std::cout << "Constured Entity!" << std::endl;
}
~Entity() {
std::cout << "Constured Entity!" << std::endl;
}
}
class ScopedPtr {
private:
Entity *m_Ptr;
public:
SscopedPtr(Entity *ptr) : m_Ptr(ptr){} // 构造函数
~SscopedPtr(Entity *ptr) { // 析构函数
delete m_Ptr;
}
}
int main() {
{
ScopedPtr p = ScopedPtr(new Entity); // 在该作用域内创建了局部变量p
} // 作用域退出时,局部变量p被释放,自动调用析构函数,进而将使用new创建Entity释放。
reutrn 0;
}
作用域指针(智能指针):就是类对指针的包装。
1.unique_ptr就是作用域指针。
#include
class Entity {
}
int main()
{
{
// 初始化unique_ptr智能指针的两种方法
std::unique_ptr e(new Entity);
std::unique_ptr e2 = std::make_unique(); // 推荐方式!
std::unique_ptr e3 = e2; // 非法!
} // 作用域退出时,e和e2被释放
}
2.shared_ptr共享指针。
#include
class Entity {
}
int main()
{
std::shared_ptr e3;
{
// 初始化shared_ptr智能指针的两种方法
std::shared_ptr e(new Entity);
std::shared_ptr e2 = std::make_shared(); // 推荐方式!
e3 = e2; // 合法!
} // 作用域退出时,e被释放,e2没有被释放
} // main函数退出时,e2被释放
3.weak_ptr弱指针。不会增加引用计数。
#include
class Entity {
}
int main()
{
std::weak_ptr e1;
{
std::shared_ptr e2 = std::make_shared(); // 推荐方式!
e31= e2; // 合法!但是不会增加e2的引用计数。
} // 作用域退出时,e2没有被释放。此时e1指向无效的指针
}
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);
m_Buffer[m_Size] = 0;
}
//String(const String& other) // c++提供的默认拷贝构造函数的实现示例,是一个浅拷贝的实现
// : m_Buffer(other.m_Buff), m_Size(other.m_Size)
//{
//}
// 禁用拷贝构造函数
//String(const String& other) = delete;
String(const String& other) // 自定义深度拷贝的拷贝构造函数
: m_Size(other.m_Size)
{
m_Buffer = new char[m_Size + 1];
memcpy(m_Buffer, other.m_Buffer, m_Size);
m_Buffer[m_Size] = 0;
}
~String() { // 析构函数
delete[] m_Buffer;
}
// String类的"[]"操作符重载函数
char& operator[](unsigned int index) {
return m_Buffer[index];
}
// 通过friend关键字,声明友元函数,在该函数内可以访问String对象的私有成员。
friend std::ostream& operator<<(std::ostream& stream, const String& string);
}
// String类的"<<"操作符重载函数
std::ostream& operator<<(std::ostream& stream, const String& string) {
stream << string.m_Buffer; // 直接访问私有成员,因为已经将该函数声明为了友元函数。
return stream
}
int main() {
String string = "Cherno";
// 此处会调用String类的拷贝构造函数。C++提供默认拷贝构造函数,仅提供浅拷贝
// 禁用拷贝构造函数后,该复制操作是非法的
String second = string;
second[2] = 'a'; // 调用String类的"[]"操作符重载函数
std::cin.get();
}
结论:类对象作为函数参数时,尽量传递const引用,否则会调用拷贝构造函数。
class Entity {
public:
void Print() {
std::cout << "hello Entity!" << std::endl;
}
}
class ScopePtr {
Private:
Entity* m_Objl
public:
ScopePtr(Entity* e) : m_Obj(e) {
}
~ScopePtr() {
delete m_Obj;
}
Entity* GetObject() {
return o_Obj;
}
// 重载了"->"操作符
Entity* operator->() {
return o_Obj;
}
}
int main() {
ScopePtr sp = new Entity();
sp.GetObject()->Print();
sp->Print(); // 该方式合法,因为->操作符被重载了
}
#include
struct Vertex {
float x, y, z;
}
// 为Vertex类型定义<<操作符的重载函数
std::ostream& operator<<(std::ostream& stream, const Vertex& vertex) {
stream << vertex.x << " , " << vertex.y << " , " << vertex.zl
return stream;
}
int main() {
std::Vector vert;
vert.push_back({1, 2, 3}); // 像动态数组vert中添加元素
vert.push_back({4, 5, 6});
// 遍历方式1
for (int i = 0; i < vert.size(); i++) {
std::cout << vert[i] <
1.windows库的提供方式(linux可参考):
libName-version-Platform/
dosc/ : 包含文档
include/ : 包含头文件
lib-mingw/ : 使用minggw编译的库文件
lib-mingw64/ : 使用minggw64编译的库文件
lib-vc2012/ : 使用vc2012编译的库文件
lib-vc2015/ : 使用vc2015编译的库文件
libName.lib : 静态库
libName.dll : 动态库
libNamedll.lib : 动态库的符号表,链接到可执行文件时使用,但是.dll库仍是需要的。注意:动态链接时,该文件可不用
2.C++源代码中,声明C语言的函数
#include
// #include
extern "C" int glfwinit();
int main() {
int ret = glfwinit();
return ret;
}
1.功能类似的函数重载,可以用模板代替
#include
#include
void Print(int value) {
std::cout << value << std::endl
}
void Print(float value) {
std::cout << value << std::endl
}
void Print(std::string value) {
std::cout << value << std::endl
}
int main() {
Print(5);
Print(1.3);
Print("Hello Cherno");
}
2.使用模板代替上例中重复的函数
#include
#include
// 在函数的返回类型前,使用template关键字定义模板
// typename和class在模板中作用是相同的,都用来定义类型符号
// 在编译阶段,编译器会根据函数的调用,将模板函数展开为具体类型的函数。因此
// 模板实际就是告诉编译器如何编码
template
void Print(T value) {
std::cout << value << std::endl
}
int main() {
Print(5);
Print(1.3);
Print("Hello Cherno");
}
#include
#include
// 在使用class关键字定义类前,声明模板
// int N: 与typename定义类型声明不同;此处的N是声明了一个int类型的符号,类似于宏。
template
class Array {
private:
T m_Array[N];
public:
int GetSize() const { return N; }
}
int main() {
Array arr1; // 创建包含5个元素的数组,元素类型是int。
Array arr2; // 创建包含10个元素的数组,元素类型是std::string
}
注意:本节创建的"变长"数组是在栈上创建的。“变长”特指的代码编写阶段;编译阶段由编译器转变为定长的。
// typedef和using都可以用于定义别名
typedef int MYINT;
using MYINT = int;
1.c++ namespace的作用:定义名称空间,避免符号命名冲突。
2.在c语言库中实现类似于c++ namespace的做法是,该c语言库中的所有外部符号都添加固定的前缀。比如GLFW库中的GFLWInit()、GLFWCreate()等。
3.绝对不要在头文件中使用using namespace XXX。
4.using namespace XXX的生效范围是当前作用域。尽量将using namespace XXX放在小的作用域内。
#include
void DoWork() {
auto tid = std::this_thread::get_id(); // 获取线程id
std::cout <<"Thread id is " << tid << std::endl;
using namespace std::literals::chrono_literals; // 使用名称空间。目的是引入下文中的睡眠“1s”
while (1) {
std::cout << "Working..." << std::endl;
std::this_thread::sleep_for(1s); // 当前线程睡眠1秒
}
}
int main() {
std::thread worker(DoWork); // 调用thread类的构造函数,创建thread对象,并执行线程函数
worker.join(); // 等待线程执行结束
}
1.chrono库是c++标准库的一部分,不需要使用操作系统库,是平台无关的。在c++11引入。
2.基于开始和结束时间点的计时
#include
#include
int main() {
using namespace std::literals::chrono_literals;
auto start = std::chrono::high_resolution_clock::now();
std::thid_thread::sleep_for(1s);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::durationduration = end - start;
std::cout << duration.count() << std::endl;
}
3.对于上例的改进,基于对象生存期,自动记录耗时
#include
#include
class Timer {
std::chrono::time_point start, end;
std::chrono::duration duration;
Timer() {
start = std::chrono::high_resolution_clock::now();
}
~Timer() {
end = std::chrono::high_resolution_clock::now();
duration = end - start;
std::cout<< "Timer took " << duration << " s " << std::endl;
}
}
void TestFunc() {
// 对象创建时,调用构造函数自动记录开始时间点;作用域结束时,调用析构函数
// 自动记录结束时间点,并输出时间间隔。
Timer tm;
for (int i = 0; i < 100; i++) {
std::cout << "Hello " << i << std::endl;
}
}
int main() {
TestFunc();
}
继承、多态、内存泄露与虚析构函数:
class Base {
private:
int* m_child;
public:
Base() {m_child = new int[5]; std::cout <<"Base constructed\n"};
~Base() {delete[] m_child; std::cout <<"Base destructed\n"};
}
class Derived: public Base { // 类Derived是类Base的子类
private:
int* m_array;
public:
Base() {m_array = new int[5]; std::cout <<"Derived constructed\n"};
~Base() {delete[] m_array; std::cout <<"Derived destructed\n"};
}
int main() {
Base *base = new Base(); // 调用构造函数Base()
delete base; // 调用析构函数~Base()
Derived *derive = new Derived(); // 调用构造函数Base()和Derived()
delete derive; // 调用析构函数~Derived()和~Base()
// 多态与内存泄露
Base *poly = new Derived(); // 调用构造函数Base()和Derived()
delete poly; // 仅调用析构函数~Base()。没有调用~Derived(),造成内存泄露
// 虚析构函数:虚函数与析构函数的结构
// 虚析构函数与普通虚函数的不同:虚析构函数要求虚析构函数,及其子类中覆写的析构函数都要执行。而普通虚函数,仅执行覆写的函数。
// 在Base类的析构函数~Base()定义前,加上virtual关键字,将其声明为虚析构函数后:
Base *poly = new Derived(); // 调用构造函数Base()和Derived()
delete poly; // 调用析构函数~Derived()和~Base()
}
1.c风格的类型转换
int a = 10;
double b = (double) a;
2.c++的类型转换:
1.多返回值处理
#include
#include
#include
std::tuple CreatePerson() {
return {"Cherno", 24};
}
int main() {
// 通过tuple方式,返回多个值,可读性较差
auto person = CreatePerson();
std::string& name = std::get<0>(person);
int age = std::get<1>(persion);
// 通过std::tie方式,返回多个值,可读性较好,但是代码行数多
std::string name;
int age;
std::tie(name, age) = CreatePersion();
// C++17中引入的结构化绑定
auto[name, age] = CreatePerson();
}
2.shared_ptr和unique_ptr的创建
struct Vector2 {
float x, y;
}
std::shared_ptr v1 = std::make_shared();
std::shared_ptr v1 = std::shared_ptr(new Vector2);
std::unique_ptr v1 = std::make_unique();
1.std::optional由C++17引入。
2.optional主要用来处理函数的返回值,更好的处理函数执行的成功与失败
#include
#include
#include
std::optional ReadFileAsString(const std::string& filepath) {
std::ifstream stream(filepath);
if (stream) {
std::string result;
// read from file
stream.close();
return result;
}
return {};
}
int main() {
std::optional data = ReadFileAsString("data.txt");
// 处理方式一:如果data中有数据,返回数据;否则返回默认值“file not present”
value = data.value_or("file not present");
// 处理方式二:直接判断data中有没有数据
if (data.has_value()) {
// value = data.value();
} else {
// file not present
}
}
1.std::variant由C++17引入。
#include
#include
int main () {
std::variant data;
data = "Cherno"; // data.index()返回0,即类型列表中的类型下标
if (auto value = std::get_if(&data)) { // 如果data中存放的是std::string类型的数据,std::get_if返回指向其内容的指针,否则返回NULL。
std::string& val = *value;
}
}
#include
#include
void PrintName(std::string_view name) {
std::count << "name: " << name << std::endl;
}
int main() {
std::string name = "Yan Chernikov";
// std::string firstName = name.substr(0, 3); // 字符串造成一次内存分配
// std::string lastName = name.substr(4, 9); // 字符串造成又一次内存分配
std::string_view firstName(name.c_str(), 3); // 无内存分配
std::string_view lastName(name.c_str() + 4, 9); // 无内存分配
}
std::async ??