cpp如何工作
预处理 include,define,if,ifdef
可以理解为copy,把include后的文件内容copy到当前的cpp中
编译 = compiling + linker
cpp -(build)-> obj -(linker)-> exe
函数的声明与定义
cpp中对函数的引用一定要有声明,类似Java中的import
但是函数的声明与定义是分开的
只有声明,单独的cpp文件build不会报错,但是linker会有错
obj中为不可读二进制机器码
设置项目的output属性输出文件,选择asm可以生成可读的机器码,函数后的一串为函数签名
visual studio中的debug和release上的性能和优化程度不同
优化过程中可以对某些无意义函数滤除
编译目标为exe就必须有入口函数,默认是main函数
编译目标为其他,例如lib,就无需入口函数
unresolved external error --- linker错误
编译过程不检查linker的方法,声明的函数在static的方法中,该static方法也没有被使用,即使函数无定义也不会报错,如下,如果不是static,就会complier error
#include <iostream>
void log(const char* message);
static int mul(int a, int b) {
log("xxx");
return a * b;
}
int main() {
}
通常
头文件.h中函数的声明
在一个cpp中对.h中对函数进行定义
其他cpp中对.h文件的#include,进行函数的调用,避免多重定义或者linker不到的问题
编译器的优化:常数折叠,#if等
static:只在本编译单元中生效
inline:短的代码,copy到引用位置
inline(内联)一般是用于短小然后用得多的代码,可以提高性能
引用
数据类型
内存大小
int -- 4bytes -- 32bit -- +- 2^31
bool -- 1byte,字节寻址(not 1bit),内存对齐之类
siezOf(int)
&,&a,int& a,不能重复引用对象
#include <iostream>
void IncrementPonit(int* num)
{
(*num)++;
}
void IncrementReference(int& num)
{
num++;
}
int main()
{
int a = 5;
IncrementPonit(&a);
Log(a); //<<<6
IncrementReference(a);
Log(a); //<<7
int b = 5;
int c = 8;
int& ref = b;
ref = c; //<<< b=8, c=8 引用不能重复引用对象
}
类
#include <iostream>
class Player
{
public:
int x, y;
int speed;
void change(int xa, int xb)
{
x += xa * speed;
y += xb * speed;
}
};
int main()
{
Player player;
player.speed = 2;
player.change(2, 3);
}
class and struct
strcut默认公有,class默认私有,strcut在c++中是为了兼容C,类似于class
struct多用于仅仅存储数据,静态数据等,内存上的区别,性能上的要求,考虑缓存命中的方向
如果还有其他的,更倾向于类(继承之类的)
#defeine struct class
struct vec2
{
float x, y;
};
//write a c++ class
class Log
{
public:
int LogLevelError = 0;
int LogLevelWarning = 1;
int LogLevelInfo = 2;
private:
int m_LogLevel; //m_表示私有
public:
void SetLevel(int level)
{
m_LogLevel = level;
}
void Error(const char* message)
{
if (m_LogLevel >= LogLevelError)
std::cout << "[Error]: " << message << std::endl;
}
void Warn(const char* message)
{
if (m_LogLevel >= LogLevelWarning)
std::cout << "[Warn]: " << message << std::endl;
}
void Info(const char* message)
{
if (m_LogLevel >= LogLevelInfo)
std::cout << "[Info]: " << message << std::endl;
}
};
int main()
{
Log log;
log.SetLevel(2);
log.Warn("Hello");
std::cin.get();
}
static in、outside class or struct
class或struct外的static修饰的在link阶段是局部的,仅对其定义的编译单元.obj可见
class或struct内的static修饰的,意味着是类所有实例共享的
//static.cpp
int s_Variable = 5;
//main.cpp
int s_Variable = 10;//build后存在link错误
-----
//static outside class
//static.cpp
static int s_Variable = 5;
//main.cpp
int s_Variable = 10;//build之后不会出现错误
-----
//static.cpp
int s_Variable = 5;
//main.cpp
extern int s_Variable;//build之后不会出现错误,外部链接
----
//static.cpp
static int s_Variable = 5;
//main.cpp
extern int s_Variable;//build之后依旧存在link错误,link不到对应变量
//方法同变量也是一样的道理
类内的static变量全局共享,static方法内无this实例指针
struct Entity
{
static int x, y;
static void Print()
{
std::cout << x << y << std::endl;
}
};
int Entity::x;
int Entity::y;
int main()
{
Entity e;
e.x = 3;
e.y = 5;
Entity::x = 4;
Entity::y = 7;
Entity::Print();
}
静态不能用非静态
struct Entity
{
int x, y;
static void Print()
{
std::cout << x << y << std::endl; //wrong,静态不能用非静态的
}
};
static void Print(Entity e)
{
std::cout << e.x << e.y << std::endl;//yes
}
int main()
{
Entity e;
e.x = 3;
e.y = 5;
}
//local static in class
void Function1()
{
int i = 1;
i++;
std::cout << i << std::endl;// always 2
}
void Function2()
{
static int i = 1;
i++;
std::cout << i << std::endl;// will increase,and outside can't get i
}
int main()
{
Function1();
Function2()
}
枚举
为了让对象指定为一些整数中的一个,或有一组想用数字表示的值
enum Example
{
A, B, C //默认0,1,2;且递增,如A=5,B,C不设置则默认6,7
};
enum Example : unsigned char //can't float,必须整数
{
A, B, C //所占内存少很多
};
int main()
{
Example value = B;
}
class Log
{
public:
enum Level
{
LogLevelError = 0, LogLevelWarning, LogLevelInfo
};
private:
Level m_LogLevel = LogLevelInfo; //m_表示私有
public:
void SetLevel(Level level)
{
m_LogLevel = level;
}
void Error(const char* message)
{
if (m_LogLevel >= LogLevelError)
std::cout << "[Error]: " << message << std::endl;
}
void Warn(const char* message)
{
if (m_LogLevel >= LogLevelWarning)
std::cout << "[Warn]: " << message << std::endl;
}
void Info(const char* message)
{
if (m_LogLevel >= LogLevelInfo)
std::cout << "[Info]: " << message << std::endl;
}
};
int main()
{
Log log;
log.SetLevel(Log::LogLevelInfo);
log.Warn("Hello");
std::cin.get();
}
构造函数
c++不会初始化内存空间
class Entity
{
public:
float X, Y;
Entity()
{
X = 0.0f;
Y = 0.0f;
}
void print()
{
std::cout << X << ", " << Y << std::endl;
}
};
class Log
{
private:
Log()
{
}//私有化
public:
Log() = delete;//没有构造函数
static void write()
{
}
};
int main()
{
Entity e;
e.print();
Log::write();
Log log;//wrong
}
析构函数
class Entity
{
public:
float X, Y;
Entity()
{
X = 0.0f;
Y = 0.0f;
}
~Entity()
{
//do something,释放资源or whatever
}
void print()
{
std::cout << X << ", " << Y << std::endl;
}
};
Function()
{
Entity e;
e.print();
e.~Entity();//析构函数的直接调用
}//或此行后会调用析构函数
int main()
{
Function();
}
继承
//层次结构,避免重复代码
class Entity
{
public:
float X, Y;
Entity()
{
X = 0.0f;
Y = 0.0f;
}
void print()
{
std::cout << X << ", " << Y << std::endl;
}
};
class Player : public Entity
{
public:
const char* Name;
void getName()
{
//do somthing
}
};
int main()
{
std::cout << sizeof(Player) << std::endl;
Player p1;
p1.print();
p1.X;
p1.getName();
std::cin.get()
}
虚函数
为什么要有虚函数
#include <iostream>
#include <string>
class Entity
{
public:
std::string GetName() {return "Entity";}
};
class Player : public Entity
{
private:
std::string m_Name;
public:
Player(const std::string& name)
:m_Name(name) {}
std::string GetName() {return m_Name;}
};
void PrintName(Entity* entity)
{
std::cout << entity->GetName() << std::endl;
}
int main() {
Entity* e = new Entity();
PrintName(e);
Player* p = new Player("cherno");
PrintName(p);
std::cin.get();
}//PrintName(e)和PrintName(p)打印出的结果相同 -> "Entity"
虚函数
//virtual function,virtual and override
#include <iostream>
#include <string>
class Entity
{
public:
virtual std::string GetName() {return "Entity";}
};
class Player : public Entity
{
private:
std::string m_Name;
public:
Player(const std::string& name)
:m_Name(name) {}
std::string GetName() override {return m_Name;}
};
void PrintName(Entity* entity)
{
std::cout << entity->GetName() << std::endl;
}
int main() {
Entity* e = new Entity();
PrintName(e);
Player* p = new Player("cherno");
PrintName(p);
std::cin.get();
}//PrintName(e)和PrintName(p)打印出的结果不同
//interface/pure vitual function
//必须要有实现
#include <iostream>
#include <string>
class Printable
{
public:
virtual std::string GetClassName() = 0;//未实现
};
class Entity : public Printable
{
public:
virtual std::string GetName() {return "Entity";}
std::string GetClassName() override {return "Entity";}
};
class Player : public Entity
{
private:
std::string m_Name;
public:
Player(const std::string& name)
:m_Name(name) {}
std::string GetName() override {return m_Name;}
std::string GetClassName() override {return "Player";}
};
class A : public Printable
{
public:
std::string GetClassName() override {return "A";}
};
void PrintName(Entity* entity)
{
std::cout << entity->GetName() << std::endl;
}
void Print(Printable* p)
{
std::cout << p->GetClassName() << std::endl;
}
int main() {
Entity* e = new Entity();
PrintName(e);
Print(e);
Player* p = new Player("cherno");
PrintName(p);
Print(p);
Print(new A()); //匿名
std::cin.get();
}
可见性
//private可见性:类,友元
//protected可见性:类,继承类
//父类中的protected作用是让子类可以访问父类中的private
//public
arrays
//arrays
int main() {
int example[5];
int* ptr = example;
for (int i = 0; i < 5; i++)
example[i] = 2;
example[2] = 5;
*(ptr + 3) = 6;
std::cout << example[2] << std::endl;
std::cout << example[3] << std::endl;
std::cin.get();
}//数组:指针+数字,偏移量由指针类型决定
----
//64位编译器,int* 8字节,char* 8字节
int a[5];
int count = sizeof(a) / sizeof(int);//5
int* example = new int[5];
int count = sizeof(example) / sizeof(int); //2
char* example = new char[5];
int count = sizeof(example) / sizeof(char);//8
----
//数组的初始化需要由明确的数
const int size = 5;
int example[size];//error,size非固定
static const int size = 5;
int example[size];//correct, size不会改变,静态变量在编译时已为其在静态区分配内存
//c++ 11
#include <array>
std::array<int, 5> another;
std::cout << another.size() << std:: endl;
string
//不会改变 const
const char* name = "cherno";
name[2] = 'a';//error
char* name = "cherno";
name[2] = 'a';//correct,但别这么搞,name本质还是指针,有些编译器会不通过
//name是个指针,读取的时候从该地址读取,直到遇到空终止字符0
//字符串例如,"cherno" 默认是char*,'c'则是char
#include <iostream> //该文件中其实也包含了string
#include <string> //但加这个文件原因:override操作符<<,允许发送字符串到流中
int main()
{
std::string name = "cherno"; // "cherno"类型:const char*
std::string name = "cherno" + " append";// error,本质不能把两个指针相加
name += " append"; //name是一个字符串,+=在string类中被重载,所以可以这么写
std::string name = std::string("cherno") + " append";
std::string << name << std::endl;
}
-----
#include <iostream>
#include <string>
void PrintString(const std::string& string) //引用,非复制,且不改变string
{
std::cout << string << std::endl;
}
int main() {
std::string name = "cherno";
PrintString(name);
std::cin.get();
}
//strings literals字符串变量
//标准库
#include <stdlib.h>
int main()
{
const char* name = u8"cherno";//1字节8比特
const wchar_t* name2 = L"cherno";//2字节
const char16_t* name3 = u"cherno"; //2个字节16比特
const char32_t* name4 = U"cherno"; //4字节32比特
}
using namespace std::string_literals;
std::string name = "cherno"s + " hello";//实现字符串拼接
//R:转义字符
const char* example = R"(line1
lin2
lin3)";
const char* ex = "line1\n"
"line2\n"
"line3\n";
//字符串字面量永远保存在内存的只读区域
char name[] = "cherno";
name[2] = 'a';
//实际的编译过程中,包含了对cherno的复制和重新赋值,涉及到编译的一些东西
const
//const 声明常量不会改变
int main()
{
const int MAX_AGE = 90;
int* a = new int;
*a = 2;
}
----
const int MAX_AGE = 90;
const int* a = new int; //int const* a = new int; 等价
*a = 2;//error 不能修改该指针指向的内容
a = (int*)&MAX_AGE;
std::cout << *a << std:endl;
-----
const int MAX_AGE = 90;
int* const a = new int;
*a = 2;
a = (int*)&MAX_AGE;//error 不能修改该指针
std::cout << *a << std:endl;
----
const int* const a = new int;//既不能修改指针也不能修改指针指向内容
//const在类中的使用
class Entity
{
private:
int m_X, m_Y;
public:
int GetX() const //声明该方法不会修改类中的变量
{
return m_X;
}
void SetX(int x)
{
m_X = x;
}
}
----
class Entity
{
private:
int* m_X, m_Y;
public:
const int* const GetX() const //不能被修改的指针,指针内容也不能被修改,该方法承诺不修改实际的entity类
{
return m_X;
}
}
int* m_X, m_Y; //m_X为指针,m_Y为int
int* m_X, *m_Y; //都为指针
mutable
mutable的用法
1.和const一起,const方法中可以修改这个变量。
2.lambda表达式(lambda表达式中改变,但外围不变)
//mutable 允许函数是常量方法,可以修改变量
class Entity
{
private:
int m_X, m_Y;
mutable var;
public:
int GetX() const //该方法不会修改类中的变量
{
var = 2;
return m_X;
}
void SetX(int x)
{
m_X = x;
}
}
class Entity
{
private:
std::string m_X, m_Y;
mutable int m_DebugCount = 0;
public:
const std::string& GetX() const //该方法不会修改类中的变量
{
m_DebugCount++;
return m_X;
}
void SetX(int x)
{
m_X = x;
}
};
int main()
{
const Entity e;
e.GetX();
int x = 4;
auto f = [=]() mutable //没有mutable关键字就用不了变量x
{
x++;
std::cout << x << std::endl;
};
f(); //cout 5
std::cout << x << std::endl; // x=4
}
构造函数中成员初始化列表
构造函数中成员初始化列表,1.调理清晰,2.性能压榨
class Entity
{
private:
std::string m_Name;
int m_Score;
public:
Entity()
: m_Name("UnKnow"), m_Score(0) //按顺序写
{
}
Entity(const std::string& name)
: m_Name(name)
{
}
}
使用成员初始化列表的好处
不用成员初始化列表
class Example
{
public:
Example()
{
std::cout << "create with null" << std::endl;
}
Example(int x)
{
std::cout << "creste with " << x << std::endl;
}
};
class Entity
{
private:
std::string m_Name;
Example m_Example;
public:
Entity()
{
m_Name = std::string("UnKnow");
m_Example = Example(8);
}
Entity(const std::string& name)
: m_Name(name)
{
}
};
int main()
{
Entity e;//看输出,拷贝构造函数,两次Example的构造
std::cin.get();
}
用成员初始化列表
class Example
{
public:
Example()
{
std::cout << "create with null" << std::endl;
}
Example(int x)
{
std::cout << "creste with " << x << std::endl;
}
};
class Entity
{
private:
std::string m_Name;
Example m_Example;
public:
Entity()
: m_Example(8)
{
m_Name = std::string("UnKnow");
}
Entity(const std::string& name)
: m_Name(name)
{
}
};
int main()
{
Entity e;//看输出,拷贝构造函数,1次example的构造
std::cin.get();
}
三元运算符
std::string rank = s_Level > 5 ? "master" : "beginner";
创建并初始化cpp对象
//栈上创建
using String = std::string
class Entity
{
private:
String m_Name;
public:
Entity() : m_Name("unknow") {}
Entity(const String& name) : m_Name(name) {}
const String& GetName() const { return m_Name; }
};
int main()
{
Entity entity0;
Entity entity1 = Entity("cherno");
Entity* e;
{
Entity e1("cherno");
e = &e1;
}
std::cin.get();
}
//堆上创建
int main()
{
Entity* e;
{
Entity* e1 = new Entity("cherno");
e = e1;
std::cout << (*e1).GetName() << std::endl;
std::cout << e->GetName() << std::endl;
}
std::cin.get();
delete e1;
}//占内存小的建议栈,占内存大的建议堆
new
//new关键字,在内存的一行找到对应大小的内存块,空闲列表
//指针只是内存地址,一个整数,为啥还有类型?类型是方便操控它
//new和+-一样,都是操作符,可以override
int main()
{
int a = 2;
int* b = new int[50];//200bytes
Entity* e = new Entity();
delete e;
delete[] b;
std::cin.get();
}
隐式转换,explicit关键字
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("unknow"), m_Age(age) {}
};
void PrintEntity(const Entity& entity)
{
//do something
}
int main()
{
Entity a("cherno");
Entity b(22);
Entity a = "cherno";
Entity b = 22;//不建议这么用
PrintEntity(22);
PrintEntity("cherno");//error,两次转换,const char -- string --entity
PrintEntity(Entity("cherno"));
std::cin.get();
}
//explicit 禁止隐式转换
explicit Entity(const std::string& name) : m_Name(name), m_Age(-1) {}
c++运算符及重载
//c++运算符及重载 new delete << 等等
#include <iostream>
#include <string>
struct Vector2
{
float x, y;
Vector2(float x, float y)
: x(x), y(y) {}
Vector2 add(const Vector2& vector) const
{
return Vector2(x + vector.x, y + vector.y);
}
Vector2 operator+(const Vector2& vector) const
{
return add(vector);
}
Vector2 mul(const Vector2& vector) const
{
return Vector2(x * vector.x, y * vector.y);
}
Vector2 operator*(const Vector2& vector) const
{
return mul(vector);
}
};
int main()
{
Vector2 position(2.0f, 3.0f);
Vector2 speed(0.5f, 0.8f);
Vector2 powerup(1.5f, 1.6f);
Vector2 position2 = position.add(speed);
Vector2 position3 = position.add(speed.mul(powerup));
Vector2 position4 = position + speed * powerup;
}
this关键字 -- 指向当前对象的指针
//this 指向当前对象的指针
#include <iostream>
#include <string>
void Printable(Entity* e);
void Printable(const Entity& e);
class Entity
{
public:
int x, y;
Entity(int x, int y)
{
this->x = x;
(*this).x = x;
this->y = y;
Printable(this);
Entity& e = *this;
Printable(*this);
}
int GetX() const
{
const Entity& e = *this;
return x;
}
};
void Printable(Entity* e);
{
//do something
}
int main()
{
std::cin.get();
}
对象的生存周期
//栈上对象的生命周期{},堆上看系统
#include <iostream>
class Entity
{
public:
Entity()
{
std::cout << "creating" << std::endl;
}
~Entity()
{
std::cout << "destroying" << std::endl;
}
};
int main()
{
Entity e;
std::cin.get();
}
#include <iostream>
int* CreateArray()
{
int* array = new int[50];
std::cout << "success" <<std::endl;
return array;
}
int main()
{
int* array = CreateArray();
std::cout << *array << std::endl; //0
}
----
#include <iostream>
void CreateArray(int* array)
{
//do something
std::cout << "success" <<std::endl;
}
int main()
{
int* array[50];
CreateArray(array);
std::cout << *array << std::endl;//-1176100256
}
//智能指针,及时分配在堆上也可以有作用域,相当于封装,析构函数进行delete,自动析构
#include <iostream>
class Entity
{
public:
Entity()
{
std::cout << "creating" << std::endl;
}
~Entity()
{
std::cout << "destroying" << std::endl;
}
};
class SmartPtr
{
private:
Entity* m_ptr;
public:
SmartPtr(Entity* e)
: m_ptr(e)
{
std::cout << "creating" << std::endl;
}
~SmartPtr()
{
delete m_ptr;
std::cout << "destroying" << std::endl;
}
};
int main()
{
SmartPtr ptr = new Entity();
std::cin.get();
}
智能指针 -- 原始指针的包装
unique_ptr 不能复制,拷贝构造函数和拷贝构造操作符实际上被删除 delete
复制之后。两个指针指向同一个内存块,如果一个死了,会释放该内存块,导致另一个指针指向已经被释放的内存
#include <iostream>
#include <memory>
class Entity
{
public:
Entity()
{
std::cout << "creating" << std::endl;
}
~Entity()
{
std::cout << "destroying" << std::endl;
}
};
int main()
{
std::unique_ptr<Entity> entity(new Entity()); //可以但不建议这样写
std::unique_ptr<Entity> e1 = std::make_unique<Entity>();//可以防止构造函数有异常抛出
std::cin.get();
}
shared_ptr 可复制,引用计数
std::shared_ptr<Entity> entity(new Entity());
shared_ptr需要分配另一块内存,叫做控制块,存储引用计数,先创建new Entity,再将其传递给 shared_ptr构造函数,需要两次内存分配
一次new Entity,然后是 shared_ptr的控制内存块分配
std::shared_ptr<Entity> e1 = std::make_shared<Entity>();
#include <iostream>
#include <memory>
class Entity
{
public:
Entity()
{
std::cout << "creating" << std::endl;
}
~Entity()
{
std::cout << "destroying" << std::endl;
}
};
int main()
{
std::shared_ptr<Entity> entity(new Entity());
std::shared_ptr<Entity> e1 = std::make_shared<Entity>();
std::cin.get();
}
wake_ptr,可复制 shared_ptr ,不会增加引用计数,一般用于只是单纯的对数据验证等,不必改变实际数值
复制和拷贝
int a = 3;
int b = a;//copy value,b改变不会影响a
复制指针,复制的是内存地址,并非指针指向的内存数据
#include <iostream>
#include <string>
#include <cstring>
class String
{
private:
char* m_Buffer;
unsigned int m_len;
public:
String(const char* m_string)
{
m_len = sizeof(m_string);
m_Buffer = new char[m_len + 1];//添加终止符
memcpy(m_Buffer, m_string, m_len + 1);
}
friend std::ostream& operator<<(std::ostream& stream, const String& m_string);
};
std::ostream& operator<<(std::ostream& stream, const String& m_string)
{
stream << m_string.m_Buffer;
return stream;
}
int main() {
String m_string = "cherno";
std::cout << m_string << std::endl;
}
浅拷贝
#include <iostream>
#include <string>
#include <cstring>
class String
{
private:
char* m_Buffer;
unsigned int m_len;
public:
String(const char* m_string)
{
m_len = sizeof(m_string);
m_Buffer = new char[m_len + 1];
memcpy(m_Buffer, m_string, m_len + 1);
}
// ~String()
// {
// delete[] m_Buffer;
// }
char& operator[](unsigned int index)
{
return m_Buffer[index];
}
friend std::ostream& operator<<(std::ostream& stream, const String& m_string);
};
std::ostream& operator<<(std::ostream& stream, const String& m_string)
{
stream << m_string.m_Buffer;
return stream;
}
int main() {
String m_string = "cherno";
String second = m_string;
second[2] = 'a';
std::cout << m_string << std::endl;
std::cout << second << std::endl;
}// cout的都为charno,都被改变,若不注释析构函数,会发生double free detected,复制对象中包含指针导致二次delete[],char* m_Buffer在浅拷贝中,m_string和second持有同样指针m_Buffer
以上为浅拷贝,浅拷贝,不会去到指针的内容,单纯把指针拷贝给对象
拷贝构造函数,c++默认提供的拷贝构造函数为浅拷贝,如
String(const String& other)
: m_Buffer(other.m_Buffer), m_len(other.m_len)
{
深拷贝,复制整个对象
String(const String& other)
: m_len(other.m_len)
{
m_Buffer = new char[m_len + 1];
memcpy(m_Buffer, other.m_Buffer, m_len + 1);
}
方法中的调用,用const 引用传递对象,否则会有额外内存开销
void Printable(const String& string)
{
std::cout << string << std::endl;
}
多用const引用传递对象
箭头操作符
1、指针对方法的调用
Entity e;
e.Print();
Entity* ptr = &e;
(*ptr).Print();
Entity& entity = *ptr;
entity.Print();
ptr->Print();
ptr->x = 2;
---
#include <iostream>
#include <string>
class Entity
{
public:
int x;
public:
void Print() const { std::cout << "helllo" << std::endl;}
};
class ScopedPtr
{
private:
Entity* m_obj;
public:
ScopedPtr(Entity* entity)
: m_obj(entity)
{
}
~ScopedPtr()
{
delete m_obj;
}
Entity* operator->()
{
return m_obj;
}
const Entity* operator->() const
{
return m_obj;
}
};
int main() {
ScopedPtr ptr = new Entity();//
ptr->Print();
const ScopedPtr ptr1 = new Entity();
ptr1->Print();
}
2、指针对偏移量的获取,箭头运算符获取内存中某个值的偏移量
//有问题,存在精度问题,但视频里运算是可以的
#include <iostream>
#include <string>
struct Vector3
{
float x, y, z;
};
int main()
{
int offset = (int)&((Vector3*)0)->z;
std::cout << offset << std::endl;
}
把数据序列化为一串字符流时,计算某个东西的偏移量
动态数组vector
动态数组vector,相当于ArrayList:当超过分配的内存大小时,自动进行复制到另一个内存然后删除原对象
#include <vector>
std::vector<int> vectors;//可以用原始类型
动态数组,内存上是一条直线,连续的
std::vector<Vector3> vectors; //存储对象,内存上连续,访问方便快捷,但超过原本内存大小后,需要重新申请内存复制转移
std::vector<Vector3*> vectors;//存储指正,超出原本大小时只改变指针,不改变指针指向
但是!!!尽量使用存储对象,指针是最后的选择(指针可能会带来一些问题)
#include <iostream>
#include <string>
#include <vector>
struct Vector3
{
float x, y, z;
};
std::ostream& operator<<(std::ostream& stream, const Vector3& vector)
{
stream << vector.x << vector.y << vector.z;
return stream;
}
int main()
{
std::vector<Vector3> vectors;
vectors.push_back({1,2,3});//添加元素
vectors.push_back({4,5,6});
for(int i = 0; i < vectors.size(); i++)
std::cout << vectors[i] << std::endl;
vectors.erase(vectors.begin() + 1); //删除第二个元素
for(Vector3& v : vectors) //多使用&,避免复制
std::cout << v << std::endl;
}
vector中的优化
//std::vector 优化,重新分配问题的优化
#include <iostream>
#include <string>
#include <vector>
struct Vector3
{
float x, y, z;
Vector3(float x, float y, float z)
: x(x), y(y), z(z)
{
}
Vector3(const Vector3& other)
: x(other.x), y(other.y), z(other.z)
{
std::cout << "copy" << std::endl;
}
};
std::ostream& operator<<(std::ostream& stream, const Vector3& vector)
{
stream << vector.x << vector.y << vector.z;
return stream;
}
int main()
{
std::vector<Vector3> vectors;
vectors.push_back({1,2,3});//添加元素
vectors.push_back({4,5,6});
} // 3 copy
----
#include <iostream>
#include <string>
#include <vector>
struct Vector3
{
float x, y, z;
Vector3(float x, float y, float z)
: x(x), y(y), z(z)
{
}
Vector3(const Vector3& other)
: x(other.x), y(other.y), z(other.z)
{
std::cout << "copy" << std::endl;
}
};
std::ostream& operator<<(std::ostream& stream, const Vector3& vector)
{
stream << vector.x << vector.y << vector.z;
return stream;
}
int main()
{
std::vector<Vector3> vectors;
vectors.push_back(Vector3(1, 2, 3));//添加元素
vectors.push_back(Vector3(4, 5, 6));
vectors.push_back(Vector3(7, 8, 9));
}//6 copy
6 copy原因:
1、Vector3的构造是在main函数的当前栈帧中构造,后面的push_back是要将构建的Vector3放到vectors的内存中,所以需要从main函数中copy到vectors
优化点1:在适当位置进行Vector3的构造,在vectors的内存中构造
2、默认大小为1,所以每次push都会重新分配
优化点2:提前设定size
#include <iostream>
#include <string>
#include <vector>
struct Vector3
{
float x, y, z;
Vector3(float x, float y, float z)
: x(x), y(y), z(z)
{
}
Vector3(const Vector3& other)
: x(other.x), y(other.y), z(other.z)
{
std::cout << "copy" << std::endl;
}
};
std::ostream& operator<<(std::ostream& stream, const Vector3& vector)
{
stream << vector.x << vector.y << vector.z;
return stream;
}
int main()
{
std::vector<Vector3> vectors;
vectors.reserve(3);//指定size
vectors.emplace_back(1, 2, 3);//vectors中构造
vectors.emplace_back(4, 5, 6);
vectors.emplace_back(7, 8, 9);
}
c++中的使用库
简单理解就是,clone存储库 --- 编译,运行,没有包管理之类
使用库:以二进制文件形式进行链接,而不是获取实际依赖库的源码自己编译(但在正经项目中,最好还是自己编译源码生成库)
下载的库是多少位:取决于你的目标程序是32或64位
库:include+library
静态链接库:库会在EXE可执行文件中
动态链接库:运行时被链接,dll文件形式,DLL运行时动态链接库
主要区别:库文件是否被编译到EXE文件中,链接到EXE文件,或者单独的文件,运行时加载
include文件时
<>表示外部文件,引用的文件在外部,纯外部依赖,外部库,不合项目一起编译
""文件在sln中,和项目一起编译
头文件,提供声明,可知那些函数可使用,即有相关函数声明
linker链接器指向库文件,得到正确的函数定义,调用时执行相关代码
动态链接,链接发生在运行时
静态链接,链接发生在编译时(因此静态链接可以有更多变化,compiler和linker可以接触到更多)
cpp中创建于使用库
如何处理多返回值
1、引用,例如对函数传参,传入两个参数的引用,在函数中对这两个参数进行赋值操作
void Print(int& a, int& b)
{
a = 2;
b = 3;
}
int a;
int b;
Print(a, b);
2、指针,引用需要传入一个有效变量,指针不用(nullptr)
以上都是使用输入参数来处理多种返回类型的方法
最好的方法是:构建struct,类似类,然后设置不同的属性参数
template模板
cout可以接受任何基本类型或c++内置的类型
#include <iostream>
#include <string>
template<typename T>
void Print(T value)
{
std::cout << value << std::endl;
}
int main()
{
Print(5);
Print("cherno");
std::cin.get();
}//通过template实现方法的重载,一个template实现多种参数的传入
---指定类型不用编译器去确定类型
int main()
{
Print<int>(5);
Print<std::string>("cherno");
std::cin.get();
}
template函数只有在调用时才会真正创建,实际并不存在,
如果只是写一个template而未调用,compiler可能(各类不一样)并不会检测template中是否有错
----- template类
#include <iostream>
#include <string>
template<int N>
class Array
{
private:
int m_Size = N;
public:
int Size() const { return m_Size; }
};
int main()
{
Array<5> array;
std::cout << array.Size() << std::endl;
std::cin.get();
}
-----
#include <iostream>
#include <string>
template<typename T, int N>
class Array
{
private:
T m_Array[N];//构建T类型数组,size为N
public:
int getSize() const { return N; }
};
int main()
{
Array<std::string, 5> array;
std::cout << array.getSize() << std::endl;
std::cin.get();
}
template不宜太过复杂,太复杂真正运行时会产生困惑
使用到template的地方:日志系统(可以包含各种不同类型的统一缓冲区)
堆栈(都在内存RAM中)
栈:预定义大小的内存缓冲区,栈中数据活跃在CPU
堆:预定义默认值区域,可随程序生长
堆栈最大的不同:如何分配内存
#include <iostream>
#include <string>
struct Vectors
{
private:
float x, y, z;
public:
Vectors()
:x(11), y(12), z(13) {}
};
int main()
{
int a = 5;
int array[5];
array[0] = 0;
array[1] = 1;
array[2] = 2;
array[3] = 3;
array[4] = 4;
Vectors vector;
int* ha = new int;
*ha = 5;
int* harray = new int[5];
harray[0] = 0;
harray[1] = 1;
harray[2] = 2;
harray[3] = 3;
harray[4] = 4;
Vectors* hvector = new Vectors();
std::cin.get();
}
栈:数据在栈上(内存中)是依次连着分配的,栈指针根据变量的大小移动相应的距离(由高内存到低内存)
堆:new 需要delete,各个对象在内存上并不连续
堆栈生命周期的不同
栈的分配和释放很简单,类似于CPU的一条指令之类,释放数据中,数据不断弹出,栈指针反向移动,最后返回栈指针地址
栈上的数据在内存上紧挨,可以放到一个CPU line(CPU cache最小缓存单位)上
堆的分配 malloc
堆上的数据使用,会存在cache miss(CPU中的缓存命中率,大数据中会体现差异)
new --> 调用malloc(memory allocate),调用底层操作系统在堆上分配内存,启动程序时,将对应内存RAM分配给程序,
系统会维护一个free list(空闲列表),跟踪哪块内存空闲以及对应的位置,找到后返回对应内存块的指针,
并在free list中做记录和更新,最后delete的时候又更新(一堆麻烦事)
所以:除非需要对象作用域超出当前作用域,或对象大小大于50M等,多用栈
宏 -- 预处理器预处理,纯文本替换,多用于调试中
#include <iostream>
#include <string>
#define LOG(x) std::cout << x << std::endl
int main()
{
LOG("hello");
std::cin.get();
}
----
#include <iostream>
#include <string>
#if PRE_DEBUG == 1
#define LOG(x) std::cout << x << std::endl
#elif defined(PR_RELEASE)
#define LOG(x)
#endif
int main()
{
LOG("hello");
std::cin.get();
}//属性中要设置预处理器定义
#define MAIN int main \
{\
std::cin.get();\
}//宏要求一行,可以用反斜杠多行
auto关键字 -- 可用,不要滥用
好处:服务端改变方法返回类型,客户端无需改变
坏处:服务端改变方法返回类型,客户端无需改变,可能会破坏依赖于特定类型的代码
auto的使用场景:复杂的变量类型,类型很大
#include <iostream>
#include <string>
std::string GetName()
{
return "cherno";
}
int main()
{
auto name = GetName();
std::cin.get();
}
---
#include <iostream>
#include <string>
#include <vector>
int main()
{
std::vector<std::string> strings;
strings.push_back("apple");
strings.push_back("orange");
for (std::vector<std::string>::iterator it = strings.begin();
//for (auto it = strings.begin();
it != strings.end(); it++)
{
std::cout << *it << std::endl;
}
}
静态数组 std::array 数组大小不能改变,在创建数组时就确立了大小
#include <iostream>
#include <array>
int main()
{
std::array<int, 5> array;
array[0] = 0;
int array1[5];
array1[0] = 0;
}
std::array的创建在栈
std::vector的创建在堆
静态数组的好处:可以通过size()访问数组大小,debug模式下编译存在数组边界检查
size()函数的返回值(看源码),存储在一个模板值,不存在对内存大小或变量的访问