C++学习记录

304 阅读22分钟

cpp如何工作

预处理 includedefine,if,ifdef
可以理解为copy,把include后的文件内容copy到当前的cpp中

编译 = compiling + linker
cpp -(build)-> obj -(linker)-> exe

{0A346E64-0768-4E9C-9816-3E98C335AABC}.png

函数的声明与定义
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存储库 --- 编译,运行,没有包管理之类
使用库:以二进制文件形式进行链接,而不是获取实际依赖库的源码自己编译(但在正经项目中,最好还是自己编译源码生成库)
下载的库是多少位:取决于你的目标程序是3264位
库: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()函数的返回值(看源码),存储在一个模板值,不存在对内存大小或变量的访问