字符串字面量

特殊情况

#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)

17引用

需要有pointer(指针),即上一个视频的基础

引用其实是指针的扩展,只是指向指针的语法糖,让他更容易阅读。引用本质上是引用现有变量,不同于指针,你可以在其中创建新的指针变量让后将其设置为空指针或其他。引用本身实际上并不占据新变量,他们实际上没有存储空间。

	int a = 5;
	int* b = &a;

	//&符号是类型的一部分
	//使用ref就跟使用a的别名一样
	int& ref = a;
	//这里编译器编译后会直接设置a,而
	//不会再创建一个变量
	ref = 2;

	LOG(a);

使用地址增加#

void Increment(int* value) {
	//首先引用指针,然后增加指针处的值
	(*value)++;
}

int main()
{

	int a = 5;
	Increment(&a);
	LOG(a);

	std::cin.get();
}

使用引用实现一样的效果,但是代码更加美观

void Increment(int& value) {
	value++;
}

int main()
{

	int a = 5;
	Increment(a);
	LOG(a);

	std::cin.get();
}

引用的禁忌#

不能指向其他地方


void Increment(int& value) { 
	value++;
}

int main()
{

	int a = 5;
	int b = 8;
	//必须初始化,否则编译错误
	int& ref = a;
	
	//不会出现编译错误,但是其实没有任何效果
	ref = b;

	Increment(ref);
	LOG(b);//打印8,未改变b的值

	std::cin.get();
}

指针,则可以任意变换赋值

16指针

指针

当你写一个程序的时候,你启动他,整个应用程序就会加载到内存,告诉计算机根据代码执行的操作都被加载到内存中,这是cpu实际访问你写的变量的方式。

指针,是一个存储内存地址数字

我们需要从内存中,读取或者写入东西,指针,就是内存的地址

忘记指针的类型

例子1-无类型指针#

void* 表示我们不关心地址指向的实际数据是什么类型的

#include <iostream>
#define LOG(x) std::cout << x << std::endl

int main(){
	void* ptr=0;//0,表示这是一个无效指针
	//void* ptr=NULL;// NULL是一个# define NULL 0,其实也是0
	//void* ptr=nullptr;// c++11引入的
	std::cin.get();
}

void func(int); void func(void*); func(0);//调用func(int),而不是 func(void*),存在歧义 func(nullptr);// 明确调用 func(void*)

例子2-类型指针#

	int var = 8;
	//void* ptr = &var;
	//int* ptr=&var;
	//double* ptr=(double*)&ptr;
	std::cin.get();

调试查看ptr地址上的值

指针类型,只有在读写该地址的数据时才有用(编译器才知道读写几个字节)

	int var = 8;

	//没有指定指针类型,编译器
	//不知道怎么存数据,*ptr=10会报错
	//void* ptr =  & var;
	//*ptr = 10;

	//指针指向的位置需要一个int大小(4字节)的
	//大小来存储数据
	int* ptr = &var;
	*ptr = 10;
	LOG(var);

创建堆上面的内存