35成员变量初始化

普通的初始化方法#

#include <iostream>
#include <string>

class Entity
{
private :
	std::string m_Name;
public:
	Entity()
	{
		m_Name = "Unknown";
	}

	Entity(const std::string& name)
	{
		m_Name = name;
	}
	
	const std::string& GetName() const { return m_Name; }
};

int main()
{
	Entity e0;
	std::cout << e0.GetName() << std::endl;
	Entity e1("Cherno");
	std::cout << e1.GetName() << std::endl;
	std::cin.get();
}

初始化列表#

#include <iostream>
#include <string>

class Entity
{
private:
	int m_Score;
	std::string m_Name;

public:

	//如果不是用初始化列表,就会先调用成员变量的构造函数初始化给它默认值(这里是m_Name),然后在{}中再一次赋值
	//如果用了,那就直接使用初始化列表中的值初始化
	//第二种方式只调用了string(const char*),而第一种方式多调用了一次string()
	Entity()
		:m_Name("Unknown"),m_Score(0) //注意不论这里的书写顺序是啥
		//都是先初始化m_Score,再m_Name
	{

	}

	Entity(const std::string& name)
		: m_Name(name)
	{

	}

	const std::string& GetName() const { return m_Name; }
};

int main()
{
	Entity e0;
	std::cout << e0.GetName() << std::endl;
	Entity e1("Cherno");
	std::cout << e1.GetName() << std::endl;


	std::cin.get();
}

构造函数的“幕后”过程#

初始化阶段:以下只针对类对象,基本类型不会被自动初始化(除非是全局变量:全局变量会被清零),它们的值是内存中残留的随机垃圾值。

33-34const、mutable关键字

常量折叠、类型强转#

#include <iostream>

int main()
{
	//当你定义 const int MAX_AGE = 90; 时,编译器通常会进行常量折叠(Constant Folding)。这意味着在编译阶段,代码中所有出现 MAX_AGE 的地方都会被直接替换为数字 90;
	//所以最下面的 std::cout << MAX_AGE << std::endl; 打印90
	const  int MAX_AGE = 90;
	std::cout << MAX_AGE << std::endl;//90

	int* a = new int;

	*a = 2;
	std::cout << *a << std::endl;//2

	//如果不加 (int*)类型强转,提示 a value of type "const int *"cannot beassigned to an entity of type "int *"
	//报错原因:1. 如果你能直接把 const int* 赋值给 int*,那么你就可以通过 *a = 100; 来修改 MAX_AGE 的值。这违背了 const 的初衷。为了保证代码的健壮性,编译器禁止这种**“权限扩大”**的行为(从“只读”变成“可读写”)。
	//	2. 为什么加上(int*) 就不报错了?
	//	当你加上(int*) 时,你正在使用强制类型转换(C - style Cast)。
	//	这相当于你对编译器说:“我知道 MAX_AGE 是常数,我也知道我在把只读指针转成可读写指针,出了问题我负责,请闭嘴。” 编译器接收到了你的强制指令,于是停止报错。
	a = (int*)&MAX_AGE;
	std::cout << *a << std::endl;//90
	*a = 5;

	//如果MAX_AGE加了volatile关键字修饰,即	const volatile int MAX_AGE = 90;那
	//么不会发生常量折叠,这里应该打印的是5
	std::cout << MAX_AGE << std::endl;//90
	//MAX_AGE 对应的内存空间,通过指针非法修改成了 5
	std::cout << *a << std::endl;//5

	std::cin.get();
}

const的修饰位置#

变量中#

#include <iostream>

int main()
{
	const int MAX_AGE = 90;

	//不能修改指针指向的地址里的值(但可以修改指针指向)
	const int* a = new int;
	//int const* a = new int;//跟上一句一个意思,const都是在*前面
	//*a = 2;//报错

	//由于a已经是const int*了,
	//所以这里编译器没有像int* a时报错
	a = &MAX_AGE;

	std::cout << *a << std::endl;//90 

	//不能修改指针指向的地址里的值(但可以修改指针指向)
	int* const  b = new int;
	*b = 2; //可修改地址里的值
	//不可重新分配b指向其他地址
	//b = &MAX_AGE;//报错


	const int* const  c = new int;
	int const* const  d = new int;
	/*以下均编译不通过*/
	//*c = 2;
	//c = &MAX_AGE;
	//*d = 2;
	//d = &MAX_AGE;
	/*以上均编译不通过*/

	std::cin.get();
}

class中#

#include <iostream>

class Entity
{
private:
	int m_X, m_Y;
	static int s_X;
	int* m_XP;
public:

	//const: 在该方法中不允许修改成员变量
	//禁止修改成员变量: 在该函数内部,你不能对任何非 static 成员变量进行赋值操作。
	//禁止调用非 const 函数
	int GetX() const
	{
		//m_X = 3;
		s_X = 5;
		//test();//会报错
		return m_X;
	}

	//返回一个int指针,且该指针不能改变所存地址
	//指向的值,也不能指向其他地址
	const int* const GetXP() const
	{

		return m_XP;
	}

	void test() //const
	{

	}
};
int main()
{

	std::cin.get();
}

形参中#

#include <iostream>

class Entity
{
private:
	int m_X, m_Y;
	int m_X1;
	mutable int var;
public:

	int GetX1()  
	{
		return m_X1;
	}

	int GetX() const
	{
		//var是一个mutable变量,在const方法中可以修改它
		var = 3;
		return m_X;
	}

	void SetX(int x)
	{
		m_X = x;
	}
};

void PrintEntity1(const Entity* e)
{
	e = nullptr;//允许编译,允许指向其他
	//(*e).SetX(2);//报错,不允许修改值
	std::cout << (*e).GetX() << std::endl;
}

void PrintEntity2(const Entity& e)
{
	//e = nullptr;//不允许编译,因为是引用,不允许重新指向其他
	//e.SetX(2);//报错,不允许修改值
	std::cout << e.GetX() << std::endl;
	//不允许调用非const修饰的函数,下面会报错,
	//因为无法保证GetX1()是否修改了e,而e是const修饰的
	//std::cout << e.GetX1() << std::endl;
}

int main()
{

	std::cin.get();
}

mutable#

class成员变量#

#include <iostream>
#include <string>

class Entity
{
private:
	std::string m_Name;

	//允许标记为const的方法修改该变量
	mutable int m_DebugCount = 0;

public:

	//1.如果是 const std::string,则返回的是一个副本
	//返回一个引用
	//2.最后的const表示该函数内部不允许修改类的非static成员变量
	const std::string& GetName() const
	{
		//m_Name = "a";//非法
		m_DebugCount++;//编译通过

		//由于返回的是引用,所以这里返回了成员变
		//量 m_Name 的一个“地址”或“别名”
		return m_Name;
	}

	void MyTest()
	{

	}
};

int main()
{
	const Entity e;
	e.GetName();//调用const方法
	//e.MyTest();//不允许调用非const方法


	Entity e1;
	e1.GetName();//调用const方法
	e1.MyTest();//调用非const方法 

	std::cin.get();
}

lambda表达式中#

#include <iostream> 

int main()
{
	//lamda表达式

	int x = 1;
	int y = 8;
	const int z = 5;

	//按引用捕获所有变量
	auto f1 = [&]()
		{
			y = 3;//合法,和原变量一样
			//z = 5;//不合法,和原变量一样是const
			std::cout << "Hello" << std::endl;
		};

	//mutable 不能直接修饰普通的局部变量或全局变量,只能
	//是类成员变量
	//mutable int m2 = 0;//没这种写法,会报错

	//按值捕获所有变量
	auto f2 = [=]()
		{
			//y = 3;//不合法,默认是const
			std::cout << "Hello" << std::endl;
		};

	//按值捕获所有变量
	//默认情况下,Lambda 内部的 operator() 是 const 的,不允
	//许修改捕获的副本。加上 mutable 后,Lambda 内部就可以修改这些副本了。
	auto f2_1 = [=]() mutable
		{
			y = 3;
			std::cout << "Lambda 内部 y = " << y << std::endl;//3
		};

	std::cout << "外部原变量 y = " << y << std::endl;//8

	//按值捕获x变量
	auto f3 = [x]()
		{
			//y = 3;//不合法,默认是const
			std::cout << "Hello" << std::endl;
		};

	f1();
	f2();
	f3();
	std::cin.get();
}

字符串字面量

特殊情况

#include <iostream>
#include <stdlib.h>
#include <string>

int main()
{
	//(const char [7])"Cherno",7字节,因为末尾有一个空字符'\0'
	"Cherno";
	std::cout << "Cher\0no" << std::endl;//Cher

	const char name[8] = "Che\0rno";

	std::cin.get();
}

注意事项#

#include <iostream>
#include <stdlib.h>
#include <string>

int main()
{
	//(const char [7])"Cherno",7字节,因为末尾有一个空字符'\0'
	"Cherno";
	std::cout << "Cher\0no" << std::endl;//Cher

	const char name[8] = "Che\0rno";
	std::cout << strlen(name) << std::endl;//3,不把\0算在内

	/* 错误的,某些编译器允许编译通过,但没效果
	char* name1 = "Cherno";
	name1[2] = 'a';
	std::cout << name1 << std::endl;//Cherno
	*/

	//一定要修改的话
	char name2[] = "Cherno";
	name2[2] = 'a';
	std::cout << name2 << std::endl;//Charno

	std::cin.get();
}

不同类型的字符串#

#include <iostream>

int main()
{
	const char* name = "Cherno";
	const char* name_ = u8"Cherno";

	//字符串中的每个字符都是宽字符(wchar_t 类型),
	//通常是16位或32位,取决于系统(大小平台相关)
	//L 前缀表示这是一个宽字符字符串
	const wchar_t* name2 = L"Cherno";

	// C++11后的UTF-16字符串,固定每个字符2字节
	const char16_t* name3 = u"Cherno";

	// C++11后的UTF-32字符串,固定每个字符4字节
	const char32_t* name4 = U"Cherno";

	std::cin.get();
}

字面量运算符#

//"" 是字面量运算符名称的一部分
//它表示这个运算符处理的是字符串字面量
//其他类型的字面量有不同的前缀:
// 不同的字面量类型有不同的运算符名称:
std::string operator""s(...);      // 字符串字面量
long double operator""ld(...);     // 浮点数字面量
unsigned long long operator""ull(...); // 整数型字面量
#include <iostream>
#include <string>

#include <stdlib.h>

int main()
{
	//启用了 "text"s 语法,让创建 std::string 对象更加
	// 直观、类型安全,特别是在自动类型推导和复杂字符串场景下非常有用。
	using namespace std::string_literals;
	//报错
	//std::string name0 = "Cherno" + " hello";
	std::string name0 = std::string("Cherno") + " hello";

	//// 普通运算符重载:没有引号
	//std::string operator+(const std::string & a, const std::string & b);
	// 字面量运算符:必须有 "" 
	//std::string operator""s(const char* str, size_t len);

	//std::string operator""s(const char* str, size_t len);
	//下面的语句,相当于std::string name1 = operator""s("Cherno", 6) + " hello";
	std::string name1 = "Cherno"s + " hello";
	std::cout << name1 << std::endl;

	std::string name2 = u8"Cherno"s + " hello";
	std::wstring name3 = L"Cherno"s + L" hello";
	std::u32string name4 = U"Cherno"s + U" hello";
	//R 表示 Raw string literal(原始字符串字面量),括号内的内容会完全按照原样被存储,包括换行符、制表符等特殊字符。
	const char* example = R"(Line1
Line2
Line3
Line4)";
	/*
Line1
Line2
Line3
Line4
	*/
	std::cout << example << std::endl;
	const char* example1 = "Line1\n"
		"Line2\n"
		"Line3\n"
		"Line4";
	std::cout << example1 << std::endl;
	std::cin.get();
}

查看字面量汇编文件#

#include <iostream>

int main()
{
	char name[] = "Cherno";
	name[2] = 'a';
	std::cout << name << std::endl;

	std::cin.get();
}

31字符串

  • 字符串本质上是一组字符(字符数组),字符,即数字、字母等等
  • 需要在计算机上以某种形式表示文本
  • 如果想要在C++中实现字符串功能,先要了解字符的工作原理,以及字符是什么。
  • 如果我们只用一个字节(8位)来表示一个字符,那么就有2^8=256种可能性。但是如果考虑到字母、数字、特殊符号、中文、韩文远超过256种。所以后面才有16字符编码(UTF-16),即用16字节表示一个字符(65536种可能性)。但在C++种不使用库就只能使用UTF-8

本章节中,其实字符串指的是字符指针

字符串修改问题#

#include <iostream> 

int main()
{
	//C风格的定义字符串的方式
	//char* name = "Cherno";
	//正常情况下不修改字符串的值,
	//如果想要新的字符串,那么就要进行全新
	//的内存分配并删除旧字符串(这里不是new,不
	//需要手动delete)
	const char* name = "Cherno";
	char const* name1 = "Cherno";
	//const在*前面,不允许修改数据
	//name[2] = '1';//报错!
	//name1[2] = '1';//报错!

	char buffer[] = "Hello";
	char* const  ptr = buffer;
	//const在*后面,不允许指向另一个字符串
	//ptr = new char[3] {'a','b','c'};//报错!

	std::cin.get();
}

字符串在内存中#

#include <iostream>

int main()
{
	//关闭编译器的安全检查来强制通过下一行代码
	//项目-属性-c++-语言:符合模式-->否,宽松模式。
	// 编译器会允许一些非标准或旧版本 C++ 的写法
	//char* name = "Charno";
	//ASCII码: 43 68 61 72 6E 6F 00
	const char* name = "Charno";
	name = new char[3] {'a', 'b','\0'};//允许
	//name[2] = 'a'; //不允许
	std::cout << name << std::endl;


	std::cin.get();
}

字符串的基本原理#

字符串在内存中

30数组

数组:一个集合,一堆元素按照特定的顺序排列起来

#include <iostream> 


int main()
{
	int example[5];
	example[0] = 2;
	example[4] = 4;

	std::cout << example[0] << std::endl;
	//无效值
	std::cout << example[2] << std::endl;
	std::cout << example[4] << std::endl;
	//编译器不检查越界
	std::cout << example[-1] << std::endl;
	std::cout << example[5] << std::endl;
	//数组内存地址
	std::cout << example << std::endl;
	/*
2
-858993460
4
-858993460
-858993460
0000006DC77EFB38
	*/
	std::cin.get();
}

循环#

#include <iostream> 


int main()
{
	int example[5];
	example[0] = 2;
	example[1] = 4;
	example[2] = 2;
	example[3] = 4;
	example[4] = 2;
	for (int i = 0; i < 5; i++)
	{
		example[i] = i;
	}
	std::cin.get();
}

如果,数组在内存中是连续的同类型数值

26-29继承_虚函数_接口_可见性

继承#

例子#

#include <iostream>

class Entity
{
public:
	float X, Y;
	void Move(float xa, float ya)
	{
		X += xa;
		Y += ya;
	}
};

//public表示子类继承的父类的成员变量/方法,
//最高只能是public。如果是class Player :protected Entity
//则原来父类中的public成员变量变成protected,但是原来的protected
//和private成员变量则不变
class Player :public Entity
{
public:
	const char* Name;

	void PrintName()
	{
		std::cout << Name << std::endl;
	};
};
int main()
{
	//64位系统,Entity两个float,占用了16字节
	std::cout << sizeof(float) << std::endl;//4
	std::cout << sizeof(char*) << std::endl;//8
	std::cout << sizeof(Entity) << std::endl;//4+4=8
	std::cout << sizeof(Player) << std::endl;//4+4+8=16
	Player player;
	player.Move(5, 5);
	player.X = 2;

	std::cin.get();
}

多态:如果我现在创建一个独立的函数来打印Entity对象,例如通过访问X和Y变量并将它们打印出来。那我其实也可以将Player对象传递给该函数

24-25构造函数和析构函数

构造函数#

我们每次实例化一个对象时都会运行的一个特殊函数

例子#

#include <iostream>

class Entity
{
public:
	//C++中不处理的话默认不初始化任何成员变量
	float X, Y;

	//默认的构造函数,什么都不做
	//不写任何构造函数是会默认添加这个
	//Entity()
	//{

	//}

	//构造函数
	Entity()
	{
		X = 0.00f;
		Y = 0.00f;
	}

	//带参数的构造函数
	Entity(float x, float y) {
		X = x;
		Y = y;
	}

	void  Print() {
		std::cout << X << "," << Y << std::endl;
	}
};

int main()
{

	//Entity e;
	Entity e(10.0f, 5.0f);//栈上分配
	//Entity* e=new Entity(10.0f, 5.0f);//堆上分配
	std::cout << e.X << std::endl;
	e.Print();

	std::cin.get();
}

不希望其他人使用构造函数#

class Log
{
private:
	Log()
	{
	}
	//或者
	/*
	Log() = delete;
	*/
public:
	static void Write()
	{
	
	}
}

int main(){
	
	Log l;//这里会报错
}

析构函数#

  • 当你摧毁一个对象时,会调用析构函数,析构函数主要用来清楚任何不再需要的内存
  • 最典型的例子,如果在构造对象时在堆上分配内存,则必须在析构函数中手动清理它
#include <iostream>

class Entity
{
public:
	//C++中不处理的话默认不初始化任何成员变量
	float X, Y;

	//默认的构造函数,什么都不做
	//不写任何构造函数是会默认添加这个
	//Entity()
	//{

	//}

	//构造函数
	Entity()
	{
		X = 0.00f;
		Y = 0.00f;
		std::cout << "Created Entity!" << std::endl;

	}

	//带参数的构造函数
	Entity(float x, float y) {
		X = x;
		Y = y;
	}


	~Entity()
	{
		std::cout << "Destroyed Entity!" << std::endl;
	}

	void  Print() {
		std::cout << X << "," << Y << std::endl;
	}
};

void Function()
{
	Entity e;//栈上分配
	e.Print();
}

int main()
{
	Function();
   /*
Created Entity!
0,0
Destroyed Entity!

   */

	std::cin.get();
}

23enum

  • 枚举,其实就是一组
  • 想用整数来表示某些状态,并且给他们命名
#include <iostream>

//默认情况下是无符号整型unsigned int
enum Example:unsigned char
{
	//如果不指定的话,第一个
	//为0,然后逐个增加
	//只要没有指定的值,都会根据前一个指定的值逐个增加
	A,B,C
};


int a = 0;
int b = 1;
int c = 2;


int main()
{
	Example value = B;

	//if (value == 1) {
	if (value == B) {
		std::cout << "get" << std::endl;
	}
	std::cin.get();
}

日志系统改进#

#include <iostream>

class Log
{
public:
	enum Level
	{
		//这里不能用Error,因为下面有一个同名函数
		LevelError=0, LevelWarning, LevelInfo
	}; 
private:
	//日志实际级别
	//m开头表示类成员变量
	Level m_LogLevel = LevelInfo;
public:
	void SetLevel(Level level)
	{
		m_LogLevel = level;
	}

	//关于字符串指针,后面会讲解
	void Error(const char* message)
	{
		//因为枚举类型是整型,所以
		//这里可以比较
		if (m_LogLevel >= LevelError)
			std::cout << "[ERROR]:" << message << std::endl;
	}

	void Warn(const char* message)
	{
		if (m_LogLevel >= LevelWarning)
			std::cout << "[WARNING]:" << message << std::endl;
	}

	void Info(const char* message)
	{
		if (m_LogLevel >= LevelInfo)
			std::cout << "[INFO]:" << message << std::endl;
	}
};
 
int main()
{
	Log log;
	//警告或更严重的消息会被打印出
	log.SetLevel(Log::LevelWarning);
	log.Warn("Hello!");
	log.Error("Hello!");
	log.Info("Hello!");
	/*
	[WARNING]:Hello!
	[ERROR]:Hello!
	*/

	std::cin.get();
}

20-22面向对象

编写一个类#

#include <iostream>

class Log
{
public:
	const int LogLevelError = 0;
	const int LogLevelWarning = 1;
	const int LogLevelInfo = 2;
private:
	//日志实际级别
	//m开头表示类成员变量
	int m_LogLevel = LogLevelInfo;
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 << "[WARNING]:" << 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.LogLevelWarning);
	log.Warn("Hello!");
	log.Error("Hello!");
	log.Info("Hello!");
	/*
	[WARNING]:Hello!
	[ERROR]:Hello!
	*/

	std::cin.get();
}

静态_static#

使用场景:类/结构体内部;类/结构体外部

18-19类_基础

类_基础知识#

类:将数据和功能整合在一起

类定义及函数#

//类定义
class Player
{
	//默认是私有的
	//int x, y;
//这里的public包括了speed这个成员
public:
	int x, y;
	int speed;
};

//函数
void Move(Player& player, int xa, int ya)
{
	player.x += xa*player.speed;
	player.y += ya*player.speed;
}

int main()
{
	//player:对象
	//创建(实例化)一个对象
	Player player;
	player.x = 1;
	player.y = 2;
	player.speed = 2;
	Move(player, 1, -1);
	LOG(player.x);//3
	LOG(player.y);//0
}

类定义及方法#

//类定义
class Player
{
	//默认是私有的
	//int x, y;
//这里的public包括了speed和Move这个成员
public:
	int x, y;
	int speed;
	//方法
	void Move( int xa, int ya)
	{
		x += xa * speed;
		y += ya * speed;
	}
};



int main()
{
	//player:对象
	//创建(实例化)一个对象
	Player player;
	player.x = 1;
	player.y = 2;
	player.speed = 2;
	player.Move( 1, -1);
	LOG(player.x);//3
	LOG(player.y);//0
}

C++中结构和类的区别#

  • 类的默认成员是私有的
  • 结构的成员默认是公共的
//结构定义
struct Player
{
	//默认是public的
	int x, y;
	int speed;
	//方法
	void Move( int xa, int ya)
	{
		x += xa * speed;
		y += ya * speed;
	}
};



int main()
{
	Player player;
	player.x = 1;
	player.y = 2;
	player.speed = 2;
	player.Move( 1, -1);
	LOG(player.x);//3
	LOG(player.y);//0
}

C++中还是用struct是为了与C保持向后兼容性,但也可以用#define struct class来保证,但是可见性有点问题(默认的public在class中是private)