参考资料:
为什么学习C++
C++可以直接控制机器,效率比较搞,C++代码经过对应的编译器编译就得到机器码了。C#、java会经过虚拟机C++怎么工作起来的
main函数可以虽然有声明要int返回值,但是可以不返回,会默认返回0,只有main函数有这个特例。
<< 运算符其实是函数
hpp文件不会编译,cpp文件各自都编译为单独.obj文件,链接将这些.obj文件连接起来为.exe文件
函数声明告诉链接器存在这个函数,链接器后面会去找,找不到会得到链接错误。编译
预处理,比如#include将整个文件复制过来,设置好VS可以得到已经预处理完的.i文件。链接
将编译好的多个文件编译成一个。
编译错误unresolved external symbol,找不到定义函数,可能是没有声明或者定义函数变量
变量不同在于内存的大小头文件
如果一个函数要在其他cpp文件被用到,需要在其他cpp文件中声明说,有这个函数。如果没有#include 头文件,那么每次使用都需要写函数声明。
hash pragma once 头文件在cpp文件中只包含一次指针
就是一个整数,存放着变量的地址
指针的指针说法其实有问题,指针是个常量,没有地址,应该是指针变量的指针引用
不占用空间,不是变量,不能改变,指针的简化版本,功能简单点,使用简单点。
下面第二句只是给a变量赋值,不是改变引用的值,引用不是变量,不能改变。
int& ref = a;
ref = b;
类
类不是必要的,但可以让程序更简洁,像C程序也可以完成很多工作。static
在类之外,限制定义全局变量,只定义在一个文件之内,不会互相影响。这里注意scope和lifetime的区别,scope是在一个文件,lifetime是伴随着整个程序。如果不同文件没有用static定义相同的变量会产生链接错误。函数也是一样的。
在类之内,只要是用类都可以用到。w
在函数之内,scope是函数体内部,lifetime是从函数开始创建到程序结束。local变量单例模式使用如下。
class Player {
public:
static Player& get() {
static Player p;
return p;
}
void move() {
}
};
int main()
{
Player p = Player::get();
p.move();
return 0;
}
- enum
作用:给值赋予一个名字,是一种新的类型,可以限定对应的变量是否在定义的那几个数的范围之内,比如下面的Level level; level只能是Level中的其中一个值。其中引用的时候也可以用类来引用 Log::LogLevelWarn
#include
#include
using namespace std;
class Log {
public:
enum Level {
LogLevelInfo = 0,
LogLevelWarn = 1,
LogLevelError = 2
};
private:
Level mLogLevel = LogLevelInfo;
public:
void setLevel(Level level) {
mLogLevel = level;
}
void info(const char *message) {
if (mLogLevel <= LogLevelInfo) {
cout << "[INFO]: " << message << endl;
}
}
void warn(const char *message) {
if (mLogLevel <= LogLevelWarn) {
cout << "[WARN]: " << message << endl;
}
}
void error(const char *message) {
if (mLogLevel <= LogLevelError) {
cout << "[ERROR]: " << message << endl;
}
}
};
int main(int argv, char** argc) {
Log logger;
logger.setLevel(logger.LogLevelWarn);
logger.info("info");
logger.warn("warn");
logger.error("error");
system("pause");
}
构造器
很多时候,我们都会在某个东西创建完成后希望完成初始化,M.init(),但有时会漏掉,构造器正是这个用处。构造器和析构器有点回调的意味,会自动在构造和free的时候调用。析构器的一个应用
释放对象相关分配的内存继承
继承可以减少重复代码虚方法
应用场景:一个鸭子类继承自动物类,如果重写了move方法,鸭子对象赋给动物类型,调用move会用动物.move,但实际上我们希望调用鸭子.move,C++可能为了高效,所以才会有这样的问题,虚函数会建立一些数据结构,然后调用的时候会先从鸭子开始查起来,这样才对,但性能会略微损失。给基类方法加上virtual,会建立V table,继承的信息都放在V table里面,调用的时候先从子类的方法开始。纯虚方法
相当于java中抽象方法或者接口可见性
我们需要可见性,因为对于类之外的继承者,使用者来说,需要屏蔽掉一些东西,不能在外部使用,不然程序可能会出现奔溃等等。比如游戏的角色有坐标x,y,如果外面随意修改这些值,角色可能不会像我们想象的那样子移动,类如果可以让我们操作角色移动,那么一定提供了更好的方法。数组
int example[5];//在栈上分配内存
int[] example = new int[5];//在堆上分配内存,需要delete[] example
#include
int main()
{
std::array buff;
for (int i = 0; i < buff.size(); i++) {
buff[i] = i;
}
return 0;
}
- String
const char *string = "hahaha"; //这里字符串是存放在只读存储器
以'\0'结尾。只是一个指针。不能修改string[2]
#include
#include
int main()
{
std::string str = "Hello,World!";//helloworld这里是const char *指针
std::cout << str << std::endl;
std::cin.get();
return 0;
}
方法后面加const不能修改类元素
void method() const
{
}
上面函数在下面中有用e.method()必须是上面的const修饰的,保证不会修改到e的内容才可以允许调用,不然会出错。
void function(const Entity& e){
e.method()
}
但是呢?被method后面被const修饰还有办法修改类的一些变量,这些变量要用mutable修饰。
- Member Initializer Lists
构造函数:m_member(value)
可以令初始化更简洁
可以减少拷贝,下面代码没有使用成员初始化列表,Example产生了两次,使用成员初始化列表只会产生一次。
#include
class Example {
public:
int m_member;
Example() {
std::cout << "UNKNOWN" << std::endl;
}
Example(int x) {
m_member = x;
std::cout << "create with x" << std::endl;
}
};
class Entity {
public:
Example ex;
Entity() {
this->ex = Example(2);
}
};
int main()
{
Entity e;
std::cin.get();
return 0;
}
把Entity改为下面只会生成一个
class Entity {
public:
Example ex;
Entity() :
ex(Example(2))
{
}
};
实例化对象
Entity e;这个在C++中是在栈中生成一个对象,可以直接使用,但是在java中是一个空指针,需要进一步new 对象。
也可以是Entity e("xxxx");和Entity e = Entity("xxxx");
堆中实例化 Entity *p = new Entity();
java过来的人可能会到处new,但这在C++中并不好,因为java会自动回收,但C++不会,所以还是该用栈就用栈.new
分配内存,调用构造函数
new 的时候有用[],delete也要[]运算符重载
有时函数的调用不是很清晰,比如A.add(B.mul(C)),用运算符就是很清晰,A+B*C。但是如果人家看你代码要去看运算符重载的部分,说明你可能用的不合适。
class Vect2 {
public:
double x, y;
Vect2(double x, double y)
: x(x), y(y) { }
//第一个const代表不能修改进来的引用,第二个const表示不能修改此对象的任何值
Vect2 operator+(const Vect2& other) const{
return Vect2(x + other.x, y + other.y);
}
Vect2 operator*(const Vect2& other) const {
return Vect2(x * other.x, y * other.y);
}
};
std::ostream& operator<<(std::ostream& stream, const Vect2& other) {
stream << other.x << "," << other.y;
return stream;
}
int main(int argv, char** argc) {
Vect2 v1(1.0 , 1.0);
Vect2 v2(2.0, 2.0);
Vect2 v3(3.0, 3.0);
Vect2 v4 = v1 + (v2*v3);
cout << v4 << endl;
system("pause");
}
const
Entity* const & e = this;
Entity* const e = this;
上面两个式子是等价的,而且不允许 e 和 this 的赋值。智能指针
删掉指针同时自动free指向的内存,。
如果使用Entity* e = new Entity();
,main中new是在堆分配的,不会被释放,内存泄漏。
同时注意ScopedPtr e = new Entity();
采用了隐式转化,因为右边是一个Entity*类型,左边是ScopedPtr,那怎么还可以赋值呢?这实际上相当于ScopedPtr e(new Entity());
。
智能指针结合了栈和生成器,析构器的特点,跟回调是一样的,同样还可以用于一段程序的时间测量,也可以用锁锁住一小段程序。
#include
#include
using namespace std;
class Entity {
};
class ScopedPtr {
private:
Entity* mPtr;
public:
ScopedPtr(Entity *e) {
cout << "create pointer" << endl;
}
~ScopedPtr() {
delete mPtr;
cout << "free memory" << endl;
}
};
int main(int argv, char** argc) {
{
ScopedPtr e = new Entity();
}
system("pause");
}
- unique pointer
系统的另外一种智能指针,这种指针不能被复制。
unique 指针,只能有一个,因为该指针内存被free了,其他指向同一个内存的指针就废了。
class Entity {
public:
Entity() {
std::cout << "construction" << std::endl;
}
~Entity() {
std::cout << "deconstruction" << std::endl;
}
};
int main()
{
{
std::unique_ptr e0(new Entity());//right
//std::unique_ptr e = new Entity();//wrong
std::unique_ptr e1 = std::make_unique();//also right
}
std::cin.get();
return 0;
}
这种指针无法赋值,因为复制号重载被删掉了。
- 复制构造器
第45集,非常精彩。
下面的代码会抛出异常,因为e1是复制e0的,e1只是分配内存后复制了e0的全部内容,故公用new int[5]内存块,但是跳出main的时候会free两次,这个时候要重写复制构造器,然后进行分配内存和深复制。
class Entity {
public:
int *m_Buff;
Entity() {
m_Buff = new int[5];
};
~Entity() {
free(m_Buff);
}
};
int main(int argv, char** argc) {
Entity e0;
Entity e1 = e0;
}
增加复制构造器
class Entity {
public:
int m_size;
int *m_Buff;
Entity() {
m_size = 5;
m_Buff = new int[m_size];
};
~Entity() {
free(m_Buff);
}
//:之后进行浅复制,复制构造函数里面深复制
Entity(const Entity& other) : m_size(other.m_size) {
cout << "进行复制ing..." << endl;
m_Buff = new int[5];
memcpy(m_Buff, other.m_Buff, m_size);
}
};
int main(int argv, char** argc) {
Entity e0;
Entity e1 = e0;
system("pause");
}
- 参数传入用const引用 const Type&
下面的程序输出三句进行复制ing...
,说明复制了三次,原因就是调用函数的时候复制了两次,我们不希望在这里消耗性能,因此参数传入改用const引用。
class Entity {
public:
int m_size;
int *m_Buff;
Entity() {
m_size = 5;
m_Buff = new int[m_size];
};
~Entity() {
free(m_Buff);
}
//:之后进行浅复制,复制构造函数里面深复制
Entity(const Entity& other) : m_size(other.m_size) {
cout << "进行复制ing..." << endl;
m_Buff = new int[5];
memcpy(m_Buff, other.m_Buff, m_size);
}
};
void doEntity(Entity e) {
}
int main(int argv, char** argc) {
Entity e0;
Entity e1 = e0;
doEntity(e0);
doEntity(e1);
system("pause");
}
将上面的函数改为下面只会复制一次!
void doEntity(const Entity& e) {
}
栈与堆的区别
除了使用上的区别,博主主要讲了效率上的区别,栈很简单,就是移动栈顶指针,而堆需要分配内存,这个就比较耗时了。auto
主要是在变量类型明显,且比较长的时候可以省略变量类型。
记住返回引用类型的时候要用auto&vector的使用,实际上叫ArrayList更准确,即可以改变长度的数组
这个例子用法很低效率,看下一个例子的优化
class Entity {
public:
int m_size;
int *m_Buff;
Entity(int m_size) {
this->m_size = m_size;
m_Buff = new int[m_size];
};
~Entity() {
free(m_Buff);
}
//:之后进行浅复制,复制构造函数里面深复制
Entity(const Entity& other) : m_size(other.m_size) {
cout << "进行复制ing..." << endl;
m_Buff = new int[5];
memcpy(m_Buff, other.m_Buff, m_size);
}
};
ostream& operator<<(ostream& stream, const Entity& e) {
stream << "m_size : " << e.m_size << endl;
return stream;
}
int main(int argv, char** argc) {
vector entities;
entities.push_back(1);
entities.push_back(2);
entities.push_back(3);
//删除第二个
//entities.erase(entities.begin() + 1);
//注意这里要用const Entity& e才不会复制多次
for(const Entity& e:entities)
cout << e;
system("pause");
}
- vector的优化使用
上面例子中,塞进去三个元素,复制了6次!!
为什么要复制,因为其原理是首先在栈中创建对象,然后复制到vector的对应的内存单元中——堆,entities.push_back(3);
其实是entities.push_back(Entity(3));
;为什么是6次,vector首先分配固定的空间,不够了就再分配一个更大的空间,然后复制过去,几次下来就复制很多次。
下面的代码复制0次,首先我们一开始预留了足够的3个空间,如果接下来按照前面的做法是要复制3次,但是这次我们使用emplace_back,没有经过栈,直接在堆上面创建起来。
int main(int argv, char** argc) {
vector entities;
entities.reserve(3);
entities.emplace_back(1);
entities.emplace_back(2);
entities.emplace_back(3);
}
- 返回多个值
1.返回一个结构体
2.输入各个类型变量的引用,然后再函数中
【注】:由下面的观察可知,变量名就是引用;另外getTwoString中,a = string("aaaa");
是先创建一个字符串,然后再复制给a!!从一个栈复制到另外一个栈。
void getTwoString(string& a,string& b) {
a = string("aaaa");
b = string("bbbb");
}
int main(int argv, char** argc) {
string a, b;
getTwoString(a, b);
cout << a << endl << b << endl;
system("pause");
}
3.数组,vector两种方式。
4.tuple,跟python很像。
tuple getStringAndInt() {
//猜想这里string("Hello")产生在栈上,然后复制到make_pair内部的从堆上分配的内存
return make_pair(string("Hello"), 111);
}
int main(int argv, char** argc) {
auto source = getStringAndInt();
string str = get<0>(source);
int inter = get<1>(source);
cout << str << endl << inter << endl;
system("pause");
}
- 模版入门:
编译器帮你写程序,模板只是一个蓝图。在调用的时候根据类型再去生成。
如果写一个打印多种数据类型的函数
void print(T content)
中的T随着调用的类型而变成特定的类型,这个函数不是我们平常的函数,这是一个模版,在编译的时候如果没有调用函数,那么不存在,错了也没有关系,如果调用,那么就会实际生成一个函数,这跟我们自己写的效果一样,只是这样我们省了很多工作,模版实际上在帮我们写代码。实际上函数调用的时候也可以写成print
但C++会根据输入的类型自动生成int,所以下面不用,但是要知道print是多个函数。(6);
template
void print(T content) {
cout << content << endl;
}
int main(int argv, char** argc) {
print(6);
print(3.1415);
print("hello!");
system("pause");
}
再来一个例子:
class Entity {
private:
int m_Num;
public:
Entity(int num) {
m_Num = num;
}
};
template
class Array {
private:
T m_Array[N];
public:
int size() { return N; };
};
模版的弊端:可能导致程序很复杂,因为代码都是模版自动生成出来的
- 返回多个返回值
- 为什么要使用static array而不是普通数组?
1.array在这里是一个类,但由于是模版生成的,占用的内存跟标准数组是一样的,比如虽然有.size(),size并不会占用内存空间,因为模版生成的时候直接返回模版输入。
2.功能更强大,在debug会检测出数组越界,可以获取数组长度等等。
#include
int main(int argv, char** argc) {
std::array data;
data[0] = 1;
//这里会报错
data[5] = 1;
system("pause");
}
- 隐式转化
40集 Implicit Conversion and the Explicit Keyword in C++
奇怪的语法
class Entity {
private:
std::string name;
int age;
public:
Entity(std::string name) : name(name),age(-1){}
Entity(int age) :age(age),name("UNKNOWN"){}
};
void printEntity(const Entity& e) {
//...
}
int main()
{
Entity e1("Mike"); //normal1
Entity e2(22); //normal2
Entity e3 = "Stuart"; //"Stuart"这里是一个字符串类型,隐含转化
Entity e4 = 22; //隐含转化
Entity e5 = (Entity)22; //强制类型转化
printEntity(22); //隐含转化
printEntity("Ada"); //Error!"Ada"是const char[] 类型,隐含转化只能转化一次,不从const char->string->Entity
printEntity(std::string("Ada")); //normal
printEntity(Entity("Ada")); //normal
}
explicit 放在构造器前面,说明这个构造器就不能再进行隐含转化,22 -> Entity 是调用了Entity(int age)进行生成一个Entity对象的,如果变成explicit Entity(int age)
那么Entity e4 = 22;
和printEntity(22);
会失败,但是强制类型转化还是可以的。implicit意味着自动。
- cast