C++学习笔记
学习视频资料
随手记
vector是一个能够存放任意类型的动态数组
unordered_set 不能放重复元素的容器 emplace放入
std::cin.get(); //使控制器不会立马关闭,保持窗口打开
总是通过 const引用传递对象
默认 动态链接
static 静态链接(只在当前文件下使用)
导入头文件,复制其中所有内容粘贴在.cpp中
sizeof(x) x占的多少字节
count = sizeof(x) / sizeof(type)
i++ :先引用后增加,先在i所在的表达式中使用i的当前值,后让i加1
++i :先增加后引用,让i先加1,然后在i所在的表达式中使用i的新值
直接传递值和引用传递:直接传递值相当于复制一下传入,原本的值保持不变;引用的话内存地址
定义一个字典: unordered_map<int, int> dic;
由于 C++ 未提供自带的链式哈希表,因此借助一个 vector 按序存储哈希表 dic 中的 key ,第二轮遍历此 vector 即可。
排序:sort(nums.begin(),nums.end());
字符串:单引号是字符型,双引号是字符串型
使用stirng的substr函数, 其中第一个参数是起始位置, 第二个参数是子串的长度 s.substr(pos, len)
replace(#include
<
<
<string
>
>
>)
用法一:用str替换指定字符串从起始位置pos开始长度为len的字符,string& replace (size_t pos, size_t len, const string& str);
用法二:用str替换迭代器起始位置 和 结束位置 的字符string& replace (const_iterator i1, const_iterator i2, const string& str);
用法三:用substr的指定子串(给定起始位置和长度)替换从指定位置上的字符串string& replace (size_t pos, size_t len, const string& str, size_t subpos, size_t sublen);
insert
在原串下标为pos的字符前插入字符串str
动态规划:斐波那契数列需要对中间值取模,因为中间值会太大超出内存,整数溢出
basic_string& insert (size_type pos, const basic_string& str);
str从下标为pos1开始数的n个字符插在原串下标为pos的字符前
basic_string& insert (size_type pos, const basic_string& str, size_type pos1, size_type n);
在原串下标为pos的字符前插入n个字符c
basic_string& insert (size_type pos, size_type n, char c);
“1>>n”为将二进制码向右移动n位
C++是如何工作的
Compile
.cpp -> .obj(机器代码文件)
属性>预处理器>预处理到文件:可以生成.i文件,查看具体生成的代码,但是不会生成.obj
属性>输出文件>汇编程序输出:可以生成.ASM文件,查看具体的硬件代码
Error: Cxxxx
Link
.obj -> .exe(可执行文件)
Error: LNKxxxx
C++变量
int -
2
31
2^{31}
231 ~
2
31
2^{31}
231 4个bytes,每个8bit,一共32bit,其中一位代表正负
unsigned int 无符号位
char(1 byte), short, int, long, long long
float, double
bool(1 byte)
sizeof(x) 查询大小
类型* 指针 类型& 引用
C++函数
C++头文件
Log.cpp 存放函数
Head files 存放定义的函数签名
在main.cpp中只需要 #include “xxx.h”
“” 相对路径下的文件 <>编译器包含路径,一般调用C++标准库
例如 iosstream 没有.h 这是因为它是C++标准库,C语言的都有.h
C++条件与分支
C++循环
C++控制流语句
continue 直接进行下一次迭代
break 跳出循环
return
C++指针
指针是一个保存内存地址的整数
C++ 提供了两种指针运算符,一种是取地址运算符 &,一种是间接寻址运算符 *。
指针是一个包含了另一个变量地址的变量,您可以把一个包含了另一个变量地址的变量说成是"指向"另一个变量。变量可以是任意的数据类型,包括对象、结构或者指针。
取地址运算符 &
& 是一元运算符,返回操作数的内存地址。例如,如果 var 是一个整型变量,则 &var 是它的地址。该运算符与其他一元运算符具有相同的优先级,在运算时它是从右向左顺序进行的。
您可以把 & 运算符读作"取地址运算符",这意味着,&var 读作"var 的地址"。
间接寻址运算符 *
第二个运算符是间接寻址运算符 ∗ * ∗,它是& 运算符的补充。 ∗ * ∗是一元运算符,返回操作数所指定地址的变量的值。
int var = 8;
int* ptr = &var; //&询问内存地址
*ptr = 10 //*给内存地址赋值
C++引用
int var = 8;
int& ref = a; //引用,引用不占用内存地址
指针与引用是内存与数值之间的转化
C++类(面向对象编程)
C语言没有类,但是也可以用,类本质上只是一种grammar sugar,用它来组织代码使代码更容易维护。
class(类)默认是private
public: 可以在类之外的任何地方访问类里的变量
struct(结构体)默认是public
C++中的静态(static)
类或结构体外部使用static 只对定义它的translation unit(.cpp)可见,与类的所有实例共享内存
extern 在translation unit外部找
类或结构体内部使用static
局部静态(local static)在函数中声明局部静态变量的lifetime几乎是整个程序lifetime,scope只有函数本身
Variable lifetime and scope
C++中的枚举(ENUM,enumeration)
有一个数值集合,并且想要数字来表示它们,用枚举
必须是一个整数
enum Example
{
A, B, C; //默认第一个是0,然后逐渐递增
};
C++中的构造函数
在创造一个新的实例时运行
设置变量和做初始化
没有初始化的话,参数变量直接指向内存已有的东西,并不是0
当我们构造对象时,直接运行初始化代码——构造函数
对于JAVA等语言,int和float等数据基本类型会自动初始化为0,而C++不会,必须手动初始化所有基本类型
class Entity
{
public:
float X, Y;
//构造函数
Entity()
{
//默认为空,在这里初始化变量
}
};
C++中的析构函数
在销毁对象时运行
卸载变量,清理使用过的内存
class Entity
{
public:
float X, Y;
//析构函数
~Entity()
{
//do something in here
}
}
C++继承
class Entity
{
};
class Player : public Entity
{
};
C++虚函数
在子类中重写父类的function
如果想要覆写一个function,必须将基类中的基函数标记为虚函数
//基类
virtual std::string Getname() {return "xxx"; }
//子类
std::string Getname() override {return m_Name; }
内存损失:需要一个额外的内存来存储v表,使得可以分配到正确的函数,基类中要有一个成员指针,指向v表
性能损失:每次需要遍历整个v表,来确定要映射到哪个函数
纯虚函数(抽象方法/接口)
允许我们在基类中定义一个没有实现的函数,然后强制子类去实现该函数
纯虚函数必须被实现,才能创建这个类的实例
//基类
virtual std::string Getname() = 0; //必须在子类中实现,基类无法实例化
//子类
std::string Getname() override {return m_Name; }
C++的可见性
一个面向对象编程的概念,指的是类的某些成员或方法实际上有多可见
三个基础的可见性修饰符
private:(Only*)只有这个类可以访问这些变量 friend
protected:这个类和层次结构中的所有子类,可以访问这个符号
public:所有皆可访问
C++数组
本质上是一个整型指针
int example[5]; //开辟5个int型的内存地址,在栈上创建,运行到大括号结束被destroy
//用new动态分配的内存将一直存在,直到删除它
int* another = new int[5]; //在堆上创建,直到程序把它销毁前都是active
delete[] another;
//C++11
#include <array>
std::array<int, 5> example;
count = example.size();
获取Vector容器的最后一个元素
//方法一:
return vec.at(vec.size()-1);
//方法二:
return vec.back();
//
方法三:
return vec.end()-1; //注意:end指向末尾元素的下一个元素。
//方法四:
return vec.rbegin();
数组操作
vector<int> left; //定义一个空数组
left.push_back(nums[i]); //在数组后面append
left.insert(left.end(), right.begin(), right.end()); //两数组相加
count(left.begin(), left.end(), 'x'); //对x计数
find(left.begin(), left.end(), 'x'); //find() 函数本质上是一个模板函数,用于在指定范围内查找和目标元素值相等的第一个元素。
C++字符串
const char* name = "Hello"; //const can't change the char in string
// string本质上只是一个char数组
#include <string> //不导入的话可以使用但是无法送到cout流中打印到控制台
std::string name = "Hello";
name.size();
name += "world";
name.find("no");
字符串字面量实际上是数组或者指针
const char* name = "Hello"; //指针这个定义是只读状态,不可被修改
char name[] = "Hello"; //定义为数组,可以被修改
name[2] = 'a';
C++14
using namespace std::string_literals;
//字符串的append
const char* example = R"(Line1
Line2
Line3)";
const char* example = "Line1"
"Line2"
"Line3";
C++中的const
承诺某些东西是不变的
指针本身 or 指针指向的内容
class Entity
{
private:
std::string m_Name;
public:
// 1.const 不允许修改实际的类成员 如m_Name = xxx 不被允许
// 2.const 承诺不会改变类 如在main中 const Entity e; e.Getname();如果没有这个const会报错
const std::string& GetName() const
{
return m_Name;
}
};
C++中的mutable关键字
mutable int var; //可以被改变的
类中的const方法可以修改这个成员
C++的成员初始化列表
初始化成员列表和定义的顺序应该相同
多使用
class Entity
{
public:
float X, Y;
//构造函数初始化成员列表
Entity()
: X(0.0f), Y(1.1f)
{}
};
int main()
{
Entity e;
std::cout << e.X << ' ' << e.Y << std::endl;
std::cin.get();
}
C++的三元操作符
if语句的grammar sugar
static int s_Level = 1;
static int s_Speed = 5;
int main()
{
if (s_Level > 5)
s_Speed = 10;
else
s_Speed = 5;
s_Speed = s_Level > 5 ? 10 : 5;
std::string rank = s_Level > 10 ? "Master" : "Beginner";
}
创建并初始化C++对象
选择对象在栈上创建还是在堆上(new keyword)
在堆上分配对栈要花费更多时间,而且在堆上分配必须手动释放被分配的内存(delete)
JAVA所有的东西都在堆上
C#中所有类都在栈上分配
int main()
{
Entity* e;
{
//在栈上分配,{}运行完在内存上销毁
Entity entity("Hello");
e = &entity; //查看地址
std::cout << entity.GetName() <<std::endl;
}
std::cin.get(); //e = "Unkown"
}
int main()
{
Entity* e;
{
//在堆上分配,new完在内存上delete销毁
Entity* entity = new Entity("Hello");
e = entity; //查看地址
std::cout << (*entity).GetName() <<std::endl;
}
std::cin.get(); //e = "Hello"
delete e;
}
C++ new关键字
在堆上不仅分配内存,还调用构造函数
使用new在堆上创建内存,必须使用delete释放内存
placemet new 指定内存地址
int* b = new int[];
delete[] b;
C++隐式转换与explicit关键字
explicit的构造函数,意味着没有隐式的转换
C++运算符及其重载
Vector2 Add(const Vector2& other) const
{
return Vector2(x + other.x, y + other.y);
}
Vector2 operator+(const Vector2& other) const
{
return Add(other);
}
Vector2 Add(const Vector2& other) const
{
return *this + other;
}
C++的箭头运算符
指针只是一个内存地址,是个数值,不能直接调用方法
int main()
{
//方法一
Entity e;
e.Print();
//方法二
Entity* ptr = &e;
Entity& entity = *ptr; //必须逆向引用ptr
entity.Print();
//方法三
Entity* ptr = &e;
ptr->Print(); //->相当于逆向引用,从内存(pointer)中调用方法
}
还可以用于获取内存中某个值的偏移量。
C++的this关键字
访问成员函数(member function)——一个属于某一类的函数或方法,在方法内部可以引用this
this是一个指向当前对象实例的指针,该方法属于这个对象实例
// 场合一:变量与输入变量名一致,无法赋值
class Entity
{
public:
int x, y;
Entity(int x, int y)
{
// Entity* e = this;
this->x = x;
this->y = y;
}
}
// 场合二:在一个类内部调用一个类外部的函数
class Entity
{
public:
int x, y;
Entity(int x, int y)
{
// Entity* e = this;
this->x = x;
this->y = y;
PrintEntity(this);
}
}
void PrintEntity(Entity* e)
{
//do something
}
C++的对象生存期(栈作用域生存期)
基于栈的变量在一出作用域的时候就被释放了,摧毁了
C++的智能指针
自动化地在堆上创建内存(new)和释放内存(delete)
实质上是一个原始指针的包装
unique_ptr是作用域指针,在超出作用域时调用delete销毁,不能复制,因为复制后也是指向相同的内存,如果一个死亡,另一个就指向了空内存块
shared_ptr工作方式是引用计数,如果指针的引用变为0,则被销毁
shared_ptr赋值给shared_ptr会增加引用计数,但是赋值给weak_ptr不会增加
#include <memory>
int main()
{
{
std::unique_ptr<Entity> entity(new Entity());
std::unique_ptr<Entity> entity = std::mask_unique<Entity>(); //如果构造函数抛出异常会安全一点
std::shared_ptr<Entity> sharedEntity = std::mask_unique<Entity>();
}
}
C++的复制与拷贝构造函数
strcpy includes the null termination character;
memcpy(m_Buffer, string, m_size); //复制字符串
拷贝构造函数,C++会有个默认的拷贝构造函数,做的是内存复制,将other对象的内存浅层拷贝进成员变量
浅拷贝:不会去到指针的内容或者指针指向的地方,只是复制指针,所以复制出来的指向同一内存
深拷贝:复制整个对象,不仅复制指针,也复制指针所指向的内存
String(const String& other)
:m_Size(other.m_Size)
{
m_Buffer = new char[m_Size + 1];
memcpy(m_Buffer, other.m_Buffer, m_size+1);
}
总是通过 const引用传递对象
C++动态数组
Vector - ArrayList
创建可以没有固定大小
#include <vector>
std::vector<int> list;
list.push_back(0); // Python append 在main下创建,复制进vector
list.push_back(1);
for(int i = 0; i < list.size(); i++)
std::cout << list[i] <<std::endl;
for(int& v : list)
std::cout << v <<std::endl;
list.clear(); //清除数组
list.erase(list.begin() + 1); //删除数组某一位
stdvector使用优化
push_back 复制旧的,然后重新分配,慢
list.reserve(3); //扩展vector
list.emplace_back(0); // 直接在vector中创建
C++中使用库(静态链接与动态链接)
OpenGL第三方库:GLFW glfw3.dll、glfw3.lib、glfw3.dll.lib
静态链接和动态链接的主要区别是库文件是否被编译到exe文件或链接到exe文件中
静态链接:这个库会放在可执行文件(.exe)中,没有外部依赖 glfw3.lib
#include <GLFW/glfw3.h>
int a = glfwInit();
动态链接:动态链接库是在运行时被链接的,可以选择在程序运行时,装载动态链接库,有外部依赖 glfw3.dll glfw3.dll.lib
引用第三方库用< >:外部的库,不在VS中和解决方案一起编译
引用第三方库用" " :会先检查相对路径,在实际解决方案中
C++中创建与使用库
C++如何处理多返回值
Array会在栈上创建,而Vector会把它的底层存储存储在堆上,所以返回std::array会更快
tuple, pair
//用结构体做
struct result
{
std::string str;
int val;
};
C++的模板
模板并不是实际存在的,直到我们调用它
#include <iostream>
#include <string>
template<typename T> // 可以输出各种类型的value
void Print(T value)
{
std::cout << value <<std::endl;
}
template<Typename T, int N> // 在栈上生成指定类型和大小的array
class Array
{
private:
T m_Array[N];
};
Array<int, 5> array;
C++的堆与栈内存比较
int main()
{
int value = 5; //在栈上分配内存
int* hvalue = new int; //在堆上分配内存,new关键字分配内存
*hvalue = 5;
}