C++随心记
C++中的 CONST
C++中的const是表示不可修改
int main()
{
/* 对于变量而言 */
// 不可修改的常量
const int A = 10;
// 不可修改的指针指向
const int* pointer_0 = nullptr;
int const* poniter_1 = nullptr;
// 不可修改指针指向的内容
int* const poniter_2 = nullptr;
}
const也可修饰函数
class Entity
{
private:
int* pointer;
int x;
public:
/* 对于函数而言 */
// 修饰函数,使得其权限仅为读取,不可写入
int GetX() const
{
// x++; 此语句是非法的,函数被const修饰后不准更改函数外的变量
return x;
}
// 如果返回的为指针也可以用于修饰指针
// 指向内容不可变
int* const GetPoniter()
{
return pointer;
}
// 指向不可变
const int* GetPointer()
{
return pointer;
}
// 一次性写最多的合法const
const int* const GetPointer() const
{
return pointer;
}
};
C++中的mutable
和上文的const一样,都是关键字,但mutable与const相反。mutable意味着可更改的。
class Entity
{
private:
mutable int x;
int y;
public:
Entity()
: x(10), y(20)
{}
void ChangeXAndRead() const
{
x++;
std::cout << "The value of X,Y is " << x << ',' << y << std::endl;
// 这里的输出结果为11,20
// 当试图修改y的值的时候,因为y的值为非mutable修饰,所以会报错
}
};
C++中的成员初始化列表
在面向对象的语言中,多数程序员喜欢在构造器内初始化成员。在C++语法中,存在专门初始化成员的功能,此功能的优点是:避免了在定义成员时被构造,当我们定义一个变量时,如果这个变量为某个类的实例,那么在定义时会调用其构造器,当我们赋值时回再次调用构造器,这造成了性能的浪费。
class Entity
{
public:
std::string x; // 此时调用了string的构造器
Entity()
{
x = "Hello"; // 此时调用了"Hello"的构造器,并把地址给了x
}
};
// 下面是一个比较直观的例子
class Example
{
public:
Exapmle()
{
std::cout << "Example created!" << std::endl;
}
Example(int x)
{
std::cout << "Example created with x! x=" << x << std::endl;
}
};
class Entity
{
public:
Example example;
// 这里会输出Example created!
Entity()
{
example = Example(8);
// 这里会输出Example created with x! x=8
}
};
这样就输出了两次,代表着调用了两次构造器。为了节省性能,我们可以使用成员初始化列表,语法如下
class Entity
{
private:
std::string m_Name;
int m_DebugCount;
public:
Entity(const std::string& Name)
:m_Name(Name), m_DebugCount(0)
{}
};
上面写了一个有参构造器,参变量为常量引用的Name。重点是下面是成员初始化列表,用冒号隔开,冒号后为要初始化的成员,初始化的内容为后面的括号的内容。需要注意的是,成员初始化列表需要和成员变量的定义顺序相同,这一规则非严格规定,但不遵守此规则可能会导致部分成员变量无法初始化!每个成员变量之间用逗号隔开
delete的小注意
对于new出来的对象,要记得delete。要delete一块数组,要用delete[]
int main()
{
int* a = new int[10];
delete[] a; // To delete array a
return 0;
}
如果不使用delete[]的话,你删的只是指针所指的第一个元素而已。
C++的隐式类型转换
C++中的类型转换是自动的,甚至可以转换到类的构造器中,以下为实例。
class Entity
{
private:
int m_Age;
std::string m_Name;
public:
Entity(const std::string& name)
:m_Age(-1), m_Name(name)
{
std::cout << "Entity constructor with name has called!" << std::endl;
}
Entity(int age)
:m_Age(age), m_Name("Unknown")
{
std::cout << "Entity constructor with age has called!" << std::endl;
}
};
int main()
{
Entity e1 = 12; // 隐式类型转换
Entity e2 = std::string("Chreno");
return 0;
}
explicit关键字
由于隐式转换是默认的,开发者不希望自己的构造函数被隐式转换就可以添加关键字explicit来强制构造器拒绝隐式转换。例如
class Entity
{
private:
int m_Age;
std::string m_Name;
public:
Entity(const std::string& name)
:m_Age(-1), m_Name(name)
{
std::cout << "Entity constructor with name has called!" << std::endl;
}
explicit Entity(int age)
:m_Age(age), m_Name("Unknown")
{
std::cout << "Entity constructor with age has called!" << std::endl;
}
};
int main()
{
// Entity e1 = 12; // 此行由于构造器被explicit修饰故报错
Entity e2 = std::string("Chreno");
return 0;
}
C++中的运算符重载
浅谈一下重载问题,其实运算符最简单的四则运算无非就是加减乘除。我们在重载时只要跟编译器说明了这个函数就是在重载就好了,我们会用到operator标识一下我们重载的是运算符
struct Vector2
{
float x, y;
Vector2(float x, float y)
:x(x), y(y) {}
// 运算符重载
Vector2 operator+(const Vector2& other) const
{
return Vector2(x + other.x, y + other.y);
}
Vector2 operator-(const Vector2& other) const
{
return Vector2(x - other.x, y - other.y);
}
Vector2 operator*(const Vector2& other) const
{
return Vector2(x * other.x, y * other.y);
}
Vector2 operator/(const Vector2& other) const
{
return Vector2(x / other.x, y / other.y);
}
Vector2 Add(const Vector2& other) const
{
return Vector2(x + other.x, y + other.y);
}
Vector2 Multiply(const Vector2& other) const
{
return Vector2(x * other.x, y * other.y);
}
void printSelf()
{
std::cout << "Value of x:" << x << ",Value of y:" << y << std::endl;
}
};
int main()
{
Vector2 position(4.0f, 4.0f);
Vector2 speed(0.5f, 1.5f);
Vector2 powerup(1.1f, 1.1f);
Vector2 result = position.Add(speed.Multiply(powerup));
Vector2 res = position + speed * powerup;
result.printSelf();
res.printSelf();
// result equals res
return 0;
}
再补充一点,我们可以重载ostream的<<运算符来达到输出内容的目的
#include <iostream>
class Entity
{
public:
int x, y;
Entity(int x, int y)
:x(x), y(y)
{}
};
// 2、此时我们就需要重载一下运算符
std::ostream& operator<<(std::ostream& stream, const Entity& other)
{
stream << other.x << ',' << other.y << std::endl;
return stream;
}
int main()
{
Entity e(1, 2);
// std::cout << e << std::endl; // 1、这里肯定不会输出其内容,而且也会报错
// 3、然后我们再来使用这个运算符
std::cout << e << std::endl;
return 0;
}
C++智能指针
智能指针就是用std::unique_ptr<>来裹住一个对象,相对于原始指针,智能指针加了一层壳。将这个对象的栈和堆内存绑定,在跳出作用域时自动调用delete方法,语法见下
#include <iostream>
#include <memory>
class Entity
{
public:
int x, y;
Entity(int x, int y)
:x(x), y(y)
{
std::cout << "Entity created!" << std::endl;
}
~Entity()
{
std::cout << "Entity destory by itself" << std::endl;
}
};
int main()
{
{
std::unique_ptr<Entity> entity = std::make_unique<Entity>(); // 就一次内存分配,更效率
std::unique_ptr<Entity> e(new Entity(1, 2)); // 先Entity()构造分配一次内存,再给unique_ptr分内存
}
std::cout << "Program is going done~" << std::endl;
return 0;
}
但是unique_ptr不能共享指针给别人,从原理上来说,这个对象被释放以后手握这个对象的指针也都会嗝屁,实际上需要同步。从源码上来看,=号这个运算符被重载了。
下面看一下shared_ptr,一个可以共享的智能指针
#include <iostream>
#include <memory>
class Entity
{
public:
int x, y;
Entity(int x, int y)
:x(x), y(y)
{
std::cout << "Entity created!" << std::endl;
}
~Entity()
{
std::cout << "Entity destory by itself" << std::endl;
}
void print()
{
std::cout << "The value of x:" << x << ",y:" << y << std::endl;
}
};
int main()
{
std::shared_ptr<Entity> e;
{
std::shared_ptr<Entity> shared_entity = std::make_shared<Entity>(); // 性能缘由同上
std::shared_ptr<Entity> shared_entity_new(new Entity(1, 2));
e = shared_entity;
}
e->print();
std::cout << "Program is going done~" << std::endl;
return 0;
}
shared_ptr底层实现类似计数器,不分享的时候相当于在unique_ptr和计数器=0,分享一次就会计数器++。当计数器为0后,再结束生命周期就会delete这个对象本体(在堆上),否则只是释放了指针本身的内存(在栈上)。给weak_ptr分享不会增加计数器。
C++计算偏移量
计算偏移量在计算机图形学里比较多见,先写一下具体过程
#include <iostream>
struct Vector3
{
float x, y, z;
};
int main()
{
int offset = (int)&((Vector3*)nullptr)->y;
std::cout << offset << std::endl;
std::cin.get();
return 0;
}
代码中offset就是偏移量。首先将0或者nullptr强制转化为结构体或类的指针,再去通过->调用指针就会根据偏移量偏移到目标的起始位置(这就是我们需要的值),之后取这个位置的地址(因为使用0或者nullptr作为起始地址所以不用再考虑起始地址)用&取地址(内存编号为偏移量),再通过(int)强制转换成整数就得到了偏移量
C++中优化std vector三个简单方法
我们先来写一段代码实验一下
#include <iostream>
#include <vector>
struct Vertex
{
float x, y, z;
Vertex(float x, float y, float z)
: x(x), y(y), z(z)
{
}
// copy constructor
Vertex(const Vertex& vertex)
: x(vertex.x), y(vertex.y), z(vertex.z)
{
std::cout << "Copied!" << std::endl;
}
};
int main()
{
std::vector<Vertex> vertices;
vertices.push_back({ 1, 2, 3 }); //1号语句
vertices.push_back({ 4, 5, 6 }); //2号语句
vertices.push_back({ 7, 8, 9 }); //3号语句
return 0;
}
这段代码中我们规定:Vertex结构体,每当被复制后就向控制台输出
经过实际运行后,这段代码一共输出了 6 行Copied,说明在添加到vector时Vertex被复制了六次。以下是流程:执行到1号语句时vertices的承载力为0,产生新的vertices并将旧的vertices复制到新的vertices中此时输出1次Copied,到二号语句,承载力不够,接着出现新的容器,旧的vertices中已经有的Vertex和新的Vertex都会被复制到新的容器中,此时复制了两次,即输出两次Copied,以此类推三号语句输出了三次Copied,最后即输出了1+2+3次即6次Copied
这段过程看似在计算机强大算力前不复杂,但是当push_back次数多了以后复制次数就会爆炸式增长
解决方案 一
先告诉vector给留多少空间,避免了多次复制
#include <iostream>
#include <vector>
struct Vertex
{
float x, y, z;
Vertex(float x, float y, float z)
: x(x), y(y), z(z)
{
}
// copy constructor
Vertex(const Vertex& vertex)
: x(vertex.x), y(vertex.y), z(vertex.z)
{
std::cout << "Copied!" << std::endl;
}
};
int main()
{
std::vector<Vertex> vertices;
vertices.reserve(3); // 预留空间
vertices.push_back(Vertex(1, 2, 3));
vertices.push_back(Vertex(4, 5, 6));
vertices.push_back(Vertex(7, 8, 9));
return 0;
}
在这里通过reserve告诉vector给我留3个单位的空间,运行完毕后仅输出了 3 次Copied,即没有新的容器创建
解决方案 二
通过调用类或者结构体的构造器,函数括号内为构造器对应参数
#include <iostream>
#include <vector>
struct Vertex
{
float x, y, z;
Vertex(float x, float y, float z)
: x(x), y(y), z(z)
{
}
// copy constructor
Vertex(const Vertex& vertex)
: x(vertex.x), y(vertex.y), z(vertex.z)
{
std::cout << "Copied!" << std::endl;
}
};
int main()
{
std::vector<Vertex> vertices;
vertices.emplace_back(1, 2, 3);
vertices.emplace_back(4, 5, 6);
vertices.emplace_back(7, 8, 9);
return 0;
}
此方法效率和法一相同,实现机制不同
解决方案 三(二 + 一)
预留好空间后再使用emplace不会产生任何复制(预留空间>=实际需求空间)
#include <iostream>
#include <vector>
struct Vertex
{
float x, y, z;
Vertex(float x, float y, float z)
: x(x), y(y), z(z)
{
}
// copy constructor
Vertex(const Vertex& vertex)
: x(vertex.x), y(vertex.y), z(vertex.z)
{
std::cout << "Copied!" << std::endl;
}
};
int main()
{
std::vector<Vertex> vertices;
vertices.reserve(3);
vertices.emplace_back(1, 2, 3);
vertices.emplace_back(4, 5, 6);
vertices.emplace_back(7, 8, 9);
return 0;
}
此示例Vertex类没有发生复制