C++学习笔记

文章目录

1 变量和基本类型

1.1 类型

占用内存的多少是变量间的关键区别。

类型大小 / byte
short2
int4
long4
long long8
char1
float4
double8
bool1
// 查看变量类型大小
std::cout << sizeof(int) << std::endl;
float a = 5.5f;
float a = 5.5F;

1.2 带符号和无符号类型

前面可以加unsigned以扩大存储值

unsigned int = unsigned

1.3 字面值常量

向55这样的,只能用它的值来称呼它(字面),并且值不能修改(常量),叫字面值常量。每个字面值都有对应的类型。

1、整型字面值

128U // unsigned int
66L // long
7829UL // unsigned long

2、浮点型字面值

3.14159F // 单精度
12.45L // 拓展精度

3、布尔字面值和字符字面值

bool
','
'2'

4、非打印字符的转义序列

\n // 换行符
\r // 回车符
\' // 单引号
\" // 双引号
\0 // 空字符
// 打印单引号
std::cout << '\'' << std::endl;

5、字符串字面值

字符串字面值用双引号括起来,在末尾自动添加空字符以兼容C语言。

'A' // 单个字符A
"A" // A以及编译器自动在末尾添加的空字符

仅由换行符,空格,制表符分隔开的字符串字面值可以连接。

std::cout << "Hello "
             "I am "
             "Hi"
          << std::endl;

在一行的末尾添加反斜线可以将此行和下一行当作同一行处理。

std::c\
out << "Hello \
I am \
Hi"<< st\
d::endl;

1.4 变量

1.4.1 左值和右值

左值可以出现在赋值语句的左边或右边;右值只能出现在右边。

变量是左值,数字字面值是右值。

1.4.2 声明和定义

1、定义:definition

为变量分配内存空间,还可以为变量指定初始值,一个程序中,一个变量有且仅有一个定义。

2、声明:declaration

向程序表明变量的类型和名字。定义是一种声明。

3、extern 关键字

int i // declare and define
extern int i // declare but not define

1.5 作用域(scope)

大多数作用域用{ }来界定。

// 1
{
}

// 2 语句作用域
for(int val = 1; ;){
}

// 3 类作用域
class Entity{

}

定义在所有{ }外面,整个程序都可见,也就是定义在所有函数外部的名字具有全局作用域,可以在程序的任何地方访问。

定义在某个函数的作用域内,函数外不能访问,具有局部作用域

定义在for语句中,只能在for语句中使用,叫语句作用域

类作用域
当成员在类的定义体之外定义时,使用完全限定名,指出该成员是在类作用域中,另外,在其中可以直接使用类里已定义的东西。

void class1::fuc(){
}

命名空间作用域

2 表达式

2.1 算术操作符、关系操作符和逻辑操作符

1、算术操作符

优先级:正负号 > 乘 除 求余 > 加 减

2、关系操作符

!=, ==, >, >=, <, <=

3、逻辑操作符

!逻辑非
&& 逻辑与
|| 逻辑或

4、优先级

逻辑非 ! > 算术操作符 > 关系 > &&, || > 赋值=

2.2 位操作符

2.2.1 位求反

~

2.2.2 左移右移

int a = 8;

// 左移1位
int b = a << 1;
std::cout << b;
// 输出 16

// 右移2位
int c = a >> 2;
std::cout << c;
// 输出 2

3、与、或、异或

int a = 8; // 1000
int b = 13; // 1101

// 异或    
int c = a ^ b; // 0101 = 5
std::cout << c;

// 与
int d = a & b; // 1000 = 8
std::cout << d;

// 或
int e = a | b; // 1101 = 13
std::cout << e;

2.2.3 bitset对象的使用

目的:想要第27位为1,其余都为0,用于记录第27个学生通过了考试。

方法1:建立一个容量为30的bitset对象,把第27为设为1

std::bitset<30> b;
b.set(27);
// 设回0:b.reset(27);

方法2:使用位操作

把一个只有第27位为1的无符号长整数(由1和左移生成)与helper做位或操作

unsigned long helper = 0;
helper |= 1UL << 27;

2.2.4 移位操作与cout

移位操作符具有中等优先级,其优先级比算术操作符低,但比关系操作符,赋值操作符,条件操作符高。

std::cout << 10 + 55;
// 输出 65
std::cout << (10 < 8);
// 输出 0
std::cout << 10 < 8;
// error

2.3 赋值操作符

1、右结合性

int i, j;
i = j = 0; // 正确
int* p;
i = p = 0; // 错误,不同类型

2、赋值操作具有低优先级

3、复合赋值操作符

+=, -+, *=, /=, %=

<<=, >>=, &=, ^=, |=

使用复合操作时,左操作数只计算了一次;使用相同作用的长表达式时,则计算两次。

2.4 自增和自减操作符

1、首先,无论是前自增还是后自增,i 变量都首先+1,如果是前自增,那么返回+1后的结果;如果是后自增,则返回未加1之前的值。

int i = 0, j;
j = ++i; // j = 1, i = 1
j = i++; // j = 1, i = 2

2、解引用与自增

std::vector<int> vec= { 2,5,6,80 };
std::vector<int>::iterator iter = vec.begin();
while (iter != vec.end()) {
	std::cout << *iter++ << std::endl;
}
// 输出 2 5 6 80

因为后自增的优先级高于解引用,因此:

*iter++ = *(iter++)

2.5 ->操作符

C++为包含点操作符和解引用操作符的表达式提供了一个同义词: ->

解引用p指针来得到一个object,然后得到它的成员foo

(*ptr).foo;
p -> foo;

2.6 条件操作符 (三元操作符)

b = a > 5 ? 10 : 5;

if (a > 5)
	b = 10;
else
	b = 5;

可以提升效率

b = a > 5 ? a > 10 ? 15 : 105; 

在这里插入图片描述

2.7 类型转换

1、隐式转换

2、显示转换

int main() {
    double pi = 3.1415926;
    int a = (int)pi;    //c语言的旧式类型转换
    int b = pi;         //隐式类型转换
    int c = static_cast<int> (pi); //c++的新式的类型转换运算符
    return 0;
}

去掉const属性,转变为int&属性

void func(int & a){
    std::cout << "func---a:" << a << std::endl;
    a = 100; // 这里改变了引用的值,会起作用吗?需要看传递进来的变量是否是const的
}

int main() {
    const int d = 30;
    func(const_cast<int &>(d)); // 如果在func中修改了d的值,会起作用吗?不会
    std::cout << "d:" << d << std::endl;
    return 0;
}

3 指针和引用

3.1 指针

int var = 8;
int* ptr = &var;
*ptr  = 10; // var = 10
int var = 8;
int* ptr = new int;	
ptr = &var; // var = 8
*ptr = 10; // var = 10

3.1.1 void*指针

void*可以·保存任何类型对象的地址,表明该指针与以抵制有关,但不清楚这个地址上对象的类型,因此void类型指针不支持对所指对象的赋值。

int var = 8;
void * ptr = &var;

3.2 野指针

堆上申请内存,需要手动删除。不用new就不用delete

// 无初始化
int *another = new int[5];
// 有初始化,都初始化为0
int *another = new int[5]();
delete[] another;

此时, another是一个悬垂指针,因为数组已经被删除,但这个指针仍然指向原先数组的地址,最好的方法是:一旦删除了指针所指向的对象,立即将指针置为0。

another = NULL;

没有new却delete将报错。

int i = 0;
int* p = &i;
delete p; // 报错

3.2 引用

int a = 5;
int& ref = a; // 此时ref = 5
ref = 2; 
#a = 2

3.3 函数指针

bool (*ptr)(const std::string &, const std::string &);

ptr声明为指向函数的指针,它所指的函数带有两个const std::string &类型的形参和bool类型的返回值。

1、无输入且无返回值的函数指针

void HelloWorld() {
	std::cout << "Hello" << std::endl;
}

int main() {
	auto function = HelloWorld; // Hello函数的内存地址(指针)
	// 也可写作 
	void (*function)() = HelloWorld;
	function();
}
// 输出 Hello

2、有输入且无返回值的函数指针

void HelloWorld(int a) {
	std::cout << "Hello and Value = "<< a << std::endl;
}

int main() {
	void(*function)(int) = HelloWorld;
	function(5);
}
// 输出 Hello and Value = 5

3、函数指针作为形参

void HelloWorld(int value) {
	std::cout << "Hello and Value = "<< value << std::endl;
}

void ForEach(const std::vector<int>& values, void(*function)(int)) {
// 向量和函数指针为参数
	for (int value : values) {
		function(value);
	}
}

int main() {
	std::vector<int> a = { 5,3,2,6 };
	ForEach(a, HelloWorld);
}

lambda写法:

void ForEach(const std::vector<int>& values, void(*function)(int)) {
	for (int value : values) {
		function(value);
	}
}
int main() {
	std::vector<int> a = { 5,3,2,6 };
	ForEach(a, [](int value) {std::cout << "Hello and Value = " << value << std::endl; });
	std::cin.get();
}

3.4 智能指针

3.4.1 unique_ptr

unique_ptr独享被管理对象指针所有权,不能复制

#include <iostream>
#include <string>
#include <memory>

using String = std::string;

class Entity {
private:
	int x, y;
public:
	Entity() {
		std::cout << "Created" << std::endl;
	}
	
	~Entity() {
		std::cout << "Destroyed" << std::endl;
	}
	
	void Print() {}
};

int main() {

	{
		std::unique_ptr<Entity> entity = std::make_unique<Entity>();
		// 等同 std::unique_ptr<Entity> entity(new Entity());
		entity->Print();
	}
	//scope结束之后,entity将被自动销毁
	
	std::cin.get();
}

3.4.2 shared_ptr

shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,删除所指向的堆内存。

// 声明
std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>();
sharedEntity->Print();
{
	std::shared_ptr<Entity> e0;
	{
		std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>();
		e0 = sharedEntity;
	} // 此时sharedEntity 没有被destroy,因为仍有一个指针指向它
} // 此时sharedEntity 被destroy
std::cin.get();

3.4.3 weak_ptr

shared_ptr copy给另一个shared_ptr时,增加引用计数器的计数,但shared_ptr copy给weak_ptr,不增加引用计数。

{
	std::weak_ptr<Entity> e0;
	{
		std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>();
		e0 = sharedEntity;
	} // 此时sharedEntity 被destroy,此时这个weak_ptr指向一个无效的entity
}
std::cin.get();

3.5 参数传递

3.5.1 引用

引用必须用与该引用同类型的对象初始化。

int num = 10;
int &ref = num;
std::cout << ref << std::endl; // 输出 10
ref = 88;
std::cout << num << std::endl; // 输出 88

引用是别名。

引用很专一,不会换对象。

int num = 10;
int num2 = 5;
int &ref = num;
int &ref = num2; // 报错

只有const引用才能引用const

const int number = 1024;
const int &ref = number; // 正确
int &ref2 = number; // 错误
int num = 3.14;
const int &ref = num; // 正确
const int &ref1 = num + 1; // 正确,const引用可以绑定右值,可以绑定同类型对象(例如num),可以绑定不同但相关类型的对象(例如num+1)
int &ref2 = num; // 正确,非const引用只能绑定到同类型的对象

std::cout << num << std::endl; // 输出3
std::cout << ref << std::endl; // 输出3
std::cout << ref1 << std::endl; // 输出4
std::cout << ref2 << std::endl; // 输出3

const int& ref3 = ref + 1;
std::cout << ref3 << std::endl; // 输出4

3.5.2 传值与传引用

1、

在函数运行之前,形参只是一个符号,函数调用时,需要进行参数传递,也就是用实参初始化对应的形参。

C++中,有三种参数传递方式:传值、传指针、传引用

对于Swap1函数,分别将x, y的值传给了形参a, b;a, b中的值进行了交换,但x, y并未改变,因此输出x=10, y=20

对于Swap2函数,将x的地址传给形参a,则指针a指向变量x,同理指针b指向变量y;执行Swap2函数,将a指向的存储单元中的值与b指向的存储单元中的值交换了,因此输出x=20, y=10

对于Swap3函数,将变量x起了一个别名a,变量y起了一个别名b,执行交换,因此也输出x=20, y=10

#include <iostream>

void Swap1(int a, int b) {
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}
void Swap2(int* a, int* b) {
    int tmp;
    tmp = *a;
    *a = *b;
    *b = tmp;
}
void Swap3(int &a, int &b) {
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}
int main() {
    int x = 10;
    int y = 20;
    
    Swap1(x, y);
    //Swap2(&x, &y);
    //Swap3(x, y);
    std::cout << x << std::endl << y;
    std::cin.get();
}

2、更进一步

C++的值传递(pass-by-value),是从调用的地方把值复制一遍传给函数。这样做有两个问题,第一,需要将实参拷贝到形参处,形成实参的副本,有时间和空间的开销,若实参为结构体或者类的话,那么时空开销将会非常大;第二、函数中对实参副本的修改并不会影响到函数调用位置实参的值。

C++的引用传递(pass-by-reference),一方面,参数传递的是把实参的地址拷贝到形参,这样形参和实参对应的是同一块内存地址,那么对形参的修改自然会反映到实参上;另一方面,C++中指针或者引用只占4字节,所以时空开销也能接受。

& 的目的是引用,避免再了复制一个std::string,const 是为了限定它只读。

传参时 const string&(引用传递)比const string(值传递)更节省资源

// 值传参
void Print(int index) {
	std::cout << index << std::endl;
}

// 引用传参
void PrintString(const String& index) {
	std::cout << index << std::endl;
}

int main() {
	int a = 5;
	String b = "ahksh";
	Print(a);
	PrintString(b);
	std::cin.get();
}

3.5.3 数组参数的传递

在C++中,数组参数的传递属于特殊情形。

数组作为形参按传值方式声明,但实际传递的是数组的首地址,即数组名,使得形参数组和实参数组共用同一组内存单元,因此对形参做的任何改变相当于在实参数组中进行相应的处理。

两种方式等同:

function(int a[ ]);  

function(int *a)

3.6 函数的声明

// 函数声明,最好放在头文件中
void print(int* array, int size);

3.6.1 默认实参

std::string Init(int height = 10, int width = 20, char type = ' ');

int main() {
    std::string val = Init(); // 合法,等于 Init(10, 20, ' ')
    std::string val = Init(66); // 合法,等于 Init(66, 20, ' ')
}

注意,默认实参只能用于替换函数调用缺少的尾部实参。例如要给type提供实参,那么height和width也需要提供实参。

因此,在排列形参时,要把最少使用默认实参的形参排在前面。

3.7 重载函数

出现在相同作用域的两个函数,名字相同但形参表不同,则称为重载函数。

3.7.1 函数重载和重复声明的区别

1、返回类型和形参表完全相同 -> 重复声明

2、对于非引用形参,仅有const与否的差别也算作重复声明,例如:

void replace(int cnt) {
	val = cnt;
}
void replace(const int cnt) {
	val = cnt;
} // 报错,重复声明

因为不传引用或指针的情况下,函数无法修改实参,因此既可以将const对象传给const形参也可以传给非const形参,两种形参无差别。

3、仅当形参是引用或指针时,形参是否为const才对函数重载有影响。

这种写法不报错,不是重复声明,而是参数重载,编译器可以根据实参是否为const来决定调用哪一个函数。

如果传入的对象是const,则只能调用下面的;如果传入非const对象,则这两个函数都可行,因为其也可以用来初始化const引用,但最后还是会使用上面的声明,因为这是精确匹配,而下面的存在一步转换。

void replace(int& cnt) {
	val = cnt;
}
void replace(const int& cnt) {
	val = cnt;
}

4、形参表相同,返回类型不同,则第二个声明是错误的。也就是说不能仅仅基于不同的返回类型实现参数的重载。

3.7.2 重载与作用域

一般的作用域规则同样适用于重载函数名。

如果调用语句在最直接的作用域中找到了符合名字的函数,则停止向更大的作用域查找。如果找到了多个,将匹配类型,找到适合的那个;如果只找到一个,停止查找,并检查类型是否匹配,如果匹配失败,就算报错也不继续查找。

因此应将每一个版本的重载函数都声明在同一个作用域内。

3.7.3 重载确定的步骤

1、寻找候选函数

四个f都是候选函数

void f();
void f(int);
void f(int, int);
void f(double, double = 3.14);
f(5.6);

2、寻找可行函数

第二个和第四个可行,因为存在隐式转换。

3、寻找最佳匹配

第四个为最佳匹配。

如果没有明显的最佳选择,将会报错,因为是ambiguous的,例如:

f(42, 2.56);

4 关键字

4.1 continue, break, return

在循环中使用

1、continue
只能在循环体中使用,结束本次循环,进入下一轮循环

for(i=0; i<5; i++){
	if(i%2==0)
		continue
	std::cout << "Hello" <<std:: endl;
}

2、break
(1)、用在switch中,,退出switch语句
(2)、用在循环体中,退出break所在的那层循环(最近的循环)

3、return:最强大,结束整个函数

4.2 static

4.2.1 static与变量

1、

// static.cpp
static int a = 5;
// a对其他的translation unit来说不可见

2、如果一个类的a变量是static的,此类的一个实例改变了a的值,其他所有实例同时改变。

3、静态局部对象

如果一个变量位于一个函数的scope内,但生命周期跨过了这个函数的多次调用,那么应将这个对象定义为static。

这种对象一旦被创建,只有在程序结束时才会被撤销。

// 这个函数计算自己被调用的次数
int count_calls(){
	static int cnt = 0;
	return cnt++;
}

4.2.2 static与方法

static方法不能访问非静态成员变量。

因为static方法没有类的实例,每一个非static方法都会隐式地得到一个类的实例作为参数。

4.3 const

因为 const 定义后就不能修改,所以定义时必须被初始化。

const int threshold = 5; // 正确
const int y; // 报错

4.3.1 const与extern

1、把一个非const变量定义在一个文件中,做合适的声明,就可以在另外的文件中使用这个变量。

// helper.cpp
int counter = 8;

//main.cpp
extern int counter;
std::cout << counter << std::endl; // 输出 8

2、非const变量默认为extern。要使const变量能在其他文件中访问,必须显式地指定其为extern。如果不特殊说明const变量,那么它不能在其他文件中被访问。

// 正确
// helper.cpp
extern const int counter = 8;

//main.cpp
extern const int counter;
std::cout << counter << std::endl; // 输出 8

// 错误
// helper.cpp
const int counter = 8;

//main.cpp
extern const int counter;

4.3.1 const与指针

正常情况下,可以修改指针指向内存的值,也可以修改指针自己的地址。

const int value = 100;
int *a = new int;
// 1、可以修改指针a指向内存中的数值
*a = 2; 
// 2、还可以修改指针a,使其指向value
a = (int*)&value; 
std::cout<< *a;
// 输出 100
4.3.1.1 指向const对象的指针

最前面加上const后,不能改变指针指向的内存中的数据。因为这个数据是一个const值。

但可以改变指针本身,例如下面最开始指针a指向value1,可以使其指向value2。

const int value1 = 100;
const int *a = &value1;
// 也可写作 int const* a = &value1;
//*a = 2; 报错,因为不可修改指针指向内存中的数据
std::cout << *a; 
// 输出 100

const int value2 = 105;
// 使a指向value2
a = &value2;
std::cout << *a;
// 输出 105

但是,const int*类型的指针也可以指向非const数据。因此,指向const的指针可以理解为“自以为指向const的指针”。

int value = 100;
const int *a = &value;
std::cout << *a;
// 输出 100
value = 105;
std::cout << *a;
// 输出 105
4.3.1.2 const指针

中间加上const后,能改变指针指向的内存中的数据,不能改变指针本身了。

a是指向int类型对象的const指针。不能使a指向其他对象,任何给const指针赋值的操作都会报错,即使是a = a;

int value = 100;
int *const a = &value;
    
*a = 2;
std::cout << "value = " << value;
// 输出 value = 2
4.3.1.3 指向const对象的const指针

既不能修改ptr所指对象的值,也不允许修改该指针的指向。

描述:ptr首先是一个const指针,指向int类型的const对象。

const int value = 100;
const int *const ptr = &value;
4.3.1.4 与typedef
typedef std::string *pstring;
const pstring cstr;

cstr是一个指向std::string类型对象的const指针,即4.3.1.2的情况。因为const修饰的是pstring类型,这是一个指针。

4.3.2 const与类

1、如果方法不应该或者没有修改class,那么应该把该方法标记为const。
2、mutable关键字允许变量在const方法中被修改。

class Entity {
private:
	int X, Y;
	mutable int var;
public:
	int GetX() const {
		var = 2;
		return X;
	}

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

GetX()称为常量成员函数。当调用成员函数时,是使用对象来调用的,也就是一个隐含的形参this指向调用函数的对象的地址 也就是 int GetX() {return this->X;}

加上const之后,this指针变为指向const对象的指针,因此只能读取,不能修改。

4.4 auto

用于变量,自动分配类型。

auto a = 5.0f;
auto b = a;

4.5 this以及->和.

4.5.1 this是指向当前对象实例的指针

#include <iostream>
#include <string>

class Entity {
private:
	int x, y;
public:
	
	Entity(int x, int y) {
		// 方法一
		this->x = x;
		this->y = y;
		// 方法二 指针用->
		// Entity* const ptr = this;
		// ptr->x = x;
		// ptr->y = y;
		// 方法三 实例用.
		// (*this).x = x;
		// (*this).y = y;
	}

	int  GetX() const
	{
		return x;
		// return (*this).x;
		// return this->x;
		// 以上三种写法都正确
	}
};

int main() {
	Entity e(5, 6);
	std::cout << e.GetX() << std::endl;
	// Entity* ptr1 = &e;
	// std::cout << (*ptr1).GetX() << std::endl;
	// 输出5
	// std::cout << ptr1->GetX() << std::endl;
	// 输出5
	std::cin.get();
}

4.5.2 ->和.

对象加.

指针加->

#include <iostream>
#include "qxy.h"
#include <string>
#include <memory>
//#include "ClassExtend.cpp"

using String = std::string;

class Entity {
private:
	int x, y;
public:
	Entity() {}

	void Print() {
		std::cout << "hello" << std::endl;
	}
};

int main() {
	//1
	Entity e;
	e.Print();
	//2
	Entity* ptr = &e;
	ptr->Print();
	(*ptr).Print();
	//3
	Entity& entity = *ptr; // Entity entity = *ptr; 也可以
	entity.Print();
	//4
	Entity* ptr1 = new Entity();
	ptr1->Print();

	std::cin.get();
}

4.6 namespace

#include <iostream>

namespace apple {
	void print(const char* text) {
		std::cout << "Apple and " << text << std::endl;
	}
}
namespace orange {
	void print(const char* text) {
		std::cout << "Orange and " << text << std::endl;
	}
}

int main() {
	apple::print("Hello");
	//using namespace apple; // 只在作用域内有效
	//print("Hello");
	std::cin.get();

}

4.7 inline

在函数的名字前写上inline即可将这个函数定义为内联函数,解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题。

定义在类中的成员函数默认都是内联的,如果在类中未给出成员函数定义,而又想内联该函数的话,需要在类的头文件.h中定义这个函数,不能在相应的.cpp文件中定义,因为译器必须随处可见内联函数的定义。

4.8 typedef

用来定义类型的同义词。意图是隐藏,强调使用目的,简化等。

typedef int in;
in num = 3;
std::cout << num << std::endl; // 输出3

4.9 explicit

explicit关键字只需用于类内的单参数构造函数前面,防止类构造函数的隐式自动转换。
除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为explicit。

class CxString{  
public:  
    int _size;  
    explicit CxString(int size){  
        _size = size;  
    }  
};  

5 类和结构体

5.1 类和结构体的区别

区别在于可见性。

class不写默认为private,struct不写默认为public。

struct还保持了对C的兼容性。

class Player
{
public:
	int x, y;
	int speed;
	
	void move(int a, int b){
		x += a* speed;
		y += b* speed;
	}
};

5.2 访问控制 ( private, protected, public )

1、private
class内的函数或firend class可以访问private变量。
2、protected
可以在子类中访问protected变量。
保护性强于public,弱于private。
3、public

5.3 构造函数和析构函数

1、构造函数

构造函数不能声明为const

class Entity{
	public:
		Entity(); // 正确,默认构造函数
		Entity() const; // 错误
};

// 如何正确使用默认构造函数
Entity entity;
Entity entity = Entity();

2、析构函数

在类名前面加上“~”即表示析构函数。与构造函数不同的是,一个类中只允许一个析构函数存在。

如果类中无析构函数,则编译系统会默认补上一个空的析构函数。当程序结束时,会自动调用析构函数。

如果在堆上创建实例,也就是Entity* entity = new Entity(“xingyu”); ,参考7.2.2,那么需要手动析构,也就是delete entity;

#include <iostream>
#include "qxy.h"

class Entity {
public:
	int x, y;
	void Print() {
		std::cout << x << " and " << y <<std::endl;
	}
	
	//默认构造器
	Entity(){
	}
	
	//默认析构函数
	~Entity(){
	}
	
	Entity(int X, int Y) {
		x = X;
		y = Y;
	}

};

int main() {
	//Entity e = Entity(5,10);
	Entity e(6, 10);
	e.Print();
	std::cin.get();
}

5.4 构造函数的初始化列表

1、常规写法

class Entity {
private:
	int X, Y;
	std::string Name;
public:
	Entity() {
		Name = "Unknown";
	}
};

2、成员初始化列表写法

冒号和花括号之间的部分称为构造函数初始化列表,为类的一个或多个数据成员指定初值。

此写法只需要创建一个object,而1中的写法要创建两次,第一个扔掉,用一个新的对象覆盖它。因此为节省性能,应该用成员初始化列表。另外,如果Name是const,则必须用初始化列表初始化。

#include <iostream>
#include <string>

class Entity {
private:
	int X, Y;
	std::string Name;
public:
	Entity() 
		: X(5), Y(8), Name("Unknown") //按顺序写
	{
	}

	Entity(const std::string& name) 
		: Name(name)
	{
	}
};
using namespace std;
class Entity{
public:
	string i;
	int j;
	double k;
	Entity(const string &a = "5"): i(a), j(1), k(1.0){}
};

int main()
{
	Entity entity("HISAHS");
	cout << entity.i << entity.j << entity.k << endl;
	// 输出 HISAHS11

	Entity another_entity;
	cout << another_entity.i << another_entity.j << another_entity.k << endl;
	// 输出 511
}

5.5 类成员的显式初始化

对于没有定义构造函数且数据成员都为public的类,可以采用此种方式。要按照数据成员的声明次序来使用初始化式。

using namespace std;
class Entity{
public:
	string i;
	int j;
};

int main()
{
	Entity entity = {"HISAHS", 6};
	cout << entity.i << entity.j << endl;
	// 输出 HISAHS6
}

5.6 友元

授予指定的函数或类访问其非public成员的权限。

class X{
	// 整个类设为友元,Y中的函数可以访问X的所有成员
	friend class Y;
	// 将某个类的成员函数设为友元,函数名要用所属的类名加以限定
	friend Y& Y::func(Y::idx1, Y::idx2, X&);
};

5.5 继承

Player为Entity的子类

class Entity {
public:
	int x, y;
	void Move(int xa, int ya) {
		x += xa;
		y += ya;
	}
};

class Player : public Entity{
public:
	const char* Name;
	void PrintName(){
		std::cout<< Name <<std::endl;
	}
};

Player player;
player.Move(5,6);

5.6 虚析构函数

5.6.1 虚函数和纯虚函数

https://blog.youkuaiyun.com/suren_jun/article/details/128259927

5.6.2 有子类的父类的析构函数应为虚函数

1、只要你允许一个类拥有子类,那么父类的析构函数一定要标记为virtual(虚析构函数)。
2、class Derived : public Base
在这里插入图片描述
过程1:调用base的构造和析构
过程2:调用base的构造、derived的构造,derived的析构、base的析构
过程3:当base的析构函数不为虚函数时:调用base的构造、derived的构造,base的析构。可能造成内存泄漏
当base的析构函数是虚函数时,与过程2一样。~Base(){}

6 STL

6.1 标准库string类型

6.1.1 头文件

#include <string>

6.1.2 初始化

// 1
std::string s1("value");
// 2
std::string s1 = "value";
std::cout << s1 << std::endl; // 输出 value
// 3
std::string s2(5, 'a');
std::cout << s2 << std::endl; // 输出 aaaaa
// 4
std::string s3(s2);
std::cout << s3 << std::endl; // 输出 aaaaa

6.1.3 string对象的读写

读取并忽略开头的所有空白字符,读取字符直到再次遇见空白字符。

std::string s;
std::cin >> s; // 输入hello world
std::cout << s << std::endl; // 输出hello
std::string s1, s2;
std::cin >> s1 >> s2; // 输入hello world
std::cout << s1 << s2 << std::endl; // 输出helloworld

更好的方式:

std::string word;
while (std::cin >> word) {
	std::cout << word << std::endl;
}
std::cin.get();

使用getline(),这个方法不忽略换行符,也就是说,如果输入换行符,那么line将被置为空string

std::string line;
while (std::getline(std::cin, line)) {
	std::cout << line << std::endl;
}

6.1.4 string对象的操作

1、s.empty()

若s为空,返回true;若非空,返回false

2、s.size()

std::string s = "112233";
std::cout << s.size()<< std::endl; // 输出 6

3、s.find()

std::string s = "112233";
std::cout << s.find("1") << std::endl; // 输出 0

4、string对象关系操作符

逐位比较字符,区分大小写,大写要大于小写。

5、string对象的相加

要求+操作符的左右操作数必须至少有一个是std::string类型。

// 错误写法,因为"hello"和"world"为const char*,不能把两个指针()相加
std::string str = "hello" + "world";
// 正确写法1
std::string str = std::string("hello") + "world";
// 正确写法2
std::string s1 = "hello";
s1 += "world";

正确写法3:使用append()方法

std::string a = "wiiuw";
std::cout << a.append("6666q") << std::endl;

更多std::定义的操作符:https://blog.youkuaiyun.com/new9232/article/details/125818067

string s1 = "hello";
string s2 = "world";
string right = s1 + "," + "world"; // 正确
string wrong = "hello" + s2; // 错误

6、substr()

std::string s;
s.substr(i,len);
//从s的i位开始截取长度为len的串
std::string str = "xingyu";
std::cout << str.substr(0, 3) << std::endl;
// 输出 xin	

7、string对象中对字符的处理

std::string str = "Hello!";
bool ans = ispunct(str[5]); // 判断字符是否为标点符号,ans为真

char c = toupper(str[1]);
std::cout << c << std::endl; // 输出E

6.2 顺序容器:标准库vector类型(动态数组)

6.2.1 三种顺序容器及头文件

vector, list, deque

#include <vector>

6.2.2 初始化

std::vector<int> vec;
std::vector<int> vec(10, -1); // 10 elements, each set to -1
std::vector<int> vec(10); // 10 elements, each set to 0
std::vector<std::string> vec(10, "hello");
// another_svec是svec的副本
std::vector<std::string> svec = { "hello", "I", "am" };
std::vector<std::string> another_svec(svec);

// 通过迭代器可以初始化其他类型的容器内容初始化本容器
std::list<std::string> slist(svec.begin(), svec.end());

6.2.3 vector对象的操作

1、v.empty()

2、v.size()

3、添加元素

std::vector<int> v;
v.push_back(1);
v.emplace_back(6);

push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素)。

而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。

emplace_back() 可以避免创建临时对象,造成性能损失

原文链接:https://blog.youkuaiyun.com/qq_37124218/article/details/127016701

原文链接:https://blog.youkuaiyun.com/weixin_44205193/article/details/121522379
4、处理输入

std::string word;
std::vector<std::string> v;
while (std::cin >> word) {
	v.push_back(word);
}

5、打印一个vector

// 方法1
for (int i = 0; i < v.size(); i++) {
	std::cout << v[i] << std::endl;
}

// 方法2
for (int a : v) { //会复制数据
	std::cout << a << std::endl;
}

// 方法3
for (int& a : v) { //不会复制数据,传的是引用
	std::cout << a << std::endl;
}

6、函数传参

void Function(const std::vector<int>& v) {
} 
// 调用
Function(v);

7、删除vector内元素

(1)、clear()

// 清空所有
v.clear();

(2)、erase()

// 删除第二个元素
v.erase(v.begin() + 1);

v.erase(b, e) 删除迭代器b, e之间的所有元素。

std::vector<std::string> vec = { "hello", "I", "am", "name" };
vec.erase(vec.begin() + 1, vec.end() - 1);
// vec = {"hello", "name"}

(3)、pop_back();

删除最后一个

8、赋值vector与swap

// 更新v中的元素,将迭代器b和e标记范围内的元素复制到v中
v.assign(b, e);
// 将v重新设置为n个t
v.assign(n, t);
std::vector<std::string> vec1(10);
std::vector<std::string> vec2(15);
vec1.swap(vec2);
// 现在vec1中有15个元素,vec2中有10个元素。

swap操作不增删元素,常量时间完成交换。迭代器也不会失效,只是指向了不同容器的同一位置。例如iter指向vec1[3],swap之后它指向vec2[3],指向的值相同,只是储存在不同容器中。

9、反转vector

reverse(v.begin(), v.end());

10、改变vector大小

(1)、v.resize(n,m),n为resize后容器的大小,m为填充值,可省略。

std::vector<int> v;
v = { 3,4,5 };
v.resize(2);
for (auto a : v) {
	std::cout << a << std::endl;
}
// 输出 3 4 
v.resize(5, 10);
for (auto a : v) {
	std::cout << a << std::endl;
}
// 输出 3 4 10 10 10

(2)、v.reserve(n)

为容器改变开辟的空间,不能往小改

std::vector<int> v;
v = { 3,4,5 };
v.reserve(2);
for (auto a : v) {
	std::cout << a << std::endl;
}
// 输出 3 4 5

11、insert()

vec.insert(p, t) —— 参数:迭代器,元素 —— 在迭代器前面插入该元素。

std::vector<std::string> vec = { "hello", "I", "am" };
vec.insert(vec.end(), "name");

vec.insert(p, n, t) —— 在迭代器前面插入n个该元素。
vec.insert(p, b, e) —— 在迭代器p前面插入由迭代器b和e标记的元素。

12、

front();//返回容器第一个数据元素
back();//返回容器中最后一个数据元素

6.2.4 vector容器的自增长

预留容量用来存放新添加的元素,以避免重新分配容器。

std::vector<std::string> vec = { "hello", "I", "am", "name" };
vec.push_back("eh");
std::cout << vec.size() << " ";
std::cout << vec.capacity() << " ";
// 输出 5 6
// 为容器预留50的capacity
vec.reserve(50);

6.2.5 deque双向队列

#include<deque>
push_back(elem);//在容器尾部添加一个数据
push_front(elem);//在容器头部插入一个数据
pop_back();//删除容器最后一个数据
pop_front();//删除容器的第一个数据
front();//返回容器第一个数据元素
back();//返回容器中最后一个数据元素

6.6 顺序容器适配器

6.6.1 三种顺序容器适配器 (adapter)

stack和queue基于deque实现,priority_queue基于vector实现。

6.6.2 stack 栈

s.empty()
s.size()
s.pop()
s.top()
s.push(item)

6.6.3 queue 队列 (FIFO)

1、头文件

#include <queue>

2、声明

std::queue<std::string> words;

队头出队,从队尾入队。

3、方法

q.empty()
q.size()

q.front() // 返回队首元素的值
q.pop() // 删除队首元素的值

q.back() // 返回队尾元素的值
q.push(item) // 在队尾压入一个值;对于优先队列,基于优先级压入

6.6.4 priority_queue 优先队列

priority_queue允许用户为队列中的元素设置优先级。不是将新来的元素放在队列前面,而是放在比它优先级低的元素前面。

标准库默认使用元素类型的 < 操作判断优先级关系。

提供的方法,例如push(),与队列相同。

6.4 关联容器

顺序容器通过元素在容器中的位置存储和访问元素,关联容器通过key存储和读取元素。

6.4.1 关联容器类型

key, map, multimap, multiset

6.4.2 pair 类型

1、定义

在 utility 头文件中定义。

// 创建一个空的pair对象,它的两个元素分别是T1和T2类型,采用值初始化。
pair<T1, T2> p1;  
   
// 创建一个pair对象,其中first成员初始化为v1,second成员初始化为v2。       
pair<T1, T2> p1(v1, v2);   

// 以v1和v2的值创建一个新的pair对象,其元素类型分别是v1和v2的类型。
p1 = make_pair(v1, v2);    

// 两个pair对象间的小于运算,其定义遵循字典次序:如 p1.first < p2.first 或者 !(p2.first < p1.first) && (p1.second < p2.second) 则返回true。 
p1 < p2; 

// 如果两个对象的first和second依次相等,则这两个对象相等。                 
p1 == p2;

// 返回对象p1中名为first的公有数据成员                  
p1.first; 
// 返回对象p1中名为second的公有数据成员                  
p1.second;                 

2、举例

(1). 初始化

std::pair<std::string, std::string> author3 = std::make_pair("Black", "k");

typedef std::pair<std::string, std::string> Author;
Author author1("James", "p");
Author author2("White", "j");

std::cout << author1.first << ' ' << author2.second << ' ' << author3.second << std::endl;
// 输出 James j k

(2). 与哈希表一起用

pair<char,int> charint;
unordered_map<char,int> unmap;
unmap{charint(' ',0),charint('s',1),charint('d',2),charint('.',4)};

6.4.3 map 类型

1、头文件

#include <map>
#include <unordered_map>

2、map 的声明与初始化

struct CityRecord {
	std::string Name;
	uint64_t Population;
	double Latitude, Longitude;
};
// 声明
std::map<std::string, CityRecord> cityMap;
// std::unordered_map<std::string, CityRecord> cityMap;
// 初始化
cityMap["A"] = CityRecord{ "A", 50000, 2.5, 3.7 };
cityMap["B"] = CityRecord{ "B", 5800, 2.5, 3.7 };
cityMap["C"] = CityRecord{ "C", 5000, 2.5, 3.7 };
// 通过pair类型
cityMap.insert(std::make_pair("D", CityRecord{ "D", 6000, 2.5, 3.7 }));

3、使用下标访问

CityRecord& AData = cityMap["A"];
std::cout << AData.Population << std::endl;
// 输出 50000

当查找的key不存在时,map容器为这个key创建一个新元素,将它插入此map对象中,它的value采用值初始化(类元素使用默认构造函数,内置类型元素初始化为0)

下面这个例子创建map来记录单词出现的次数,注意,最开始map是空的。

map<string, int> word_count;
string word;
while(cin>>word)
	++word_count[word];

4、map的迭代

map<string, int>::const_iterator it = word_count.begin();
while(it != word_count.end()){
	std::cout << it->first;
	++it;
}

4、查找

// 查找key为2的键值对是否存在 ,若没找到则返回m.end()
map.find(2);
// 判断找到了key为2的键值对
if(map.find(2)!=map.end()) 
// 使用count,返回的是被查找元素的个数。
// 如果有,返回1;否则,返回0。注意,map中不存在相同元素,所以返回值只能是1或0。
map.count(2);

5、判断是否为空

m.empty()

6、删除

// 删除key指向的元素
map.erase(k)
// 删除迭代器指向的元素
map.erase(p)
map.erase(begin, end)

6.4.4 set 类型

map是键值对的集合,set是单纯的键的集合。当只想知道一个值是否存在时,使用set最合适。

1、头文件

#include <set>
#include <unordered_set>

2、声明以及添加元素

std::unordered_set<int> set;
set<string> set1;
set1.insert(""hello);

使用vector初始化:

std::vector<int> v = { 1,2,3 };
std::set<int> set(v.begin(), v.end());

3、查找
与map相似

if(set.find(item) != set.end()) 
set.count(item)

6.4.5 multimap和multiset

允许一个key对应多个元素。

1、查找

不允许使用下标访问,因为一个key可能对应多个值。

方法一:
使用count求出这个key出现的次数n,find操作获得指向第一个该键元素的迭代器,由于这n个元素一定是相邻存放的,因此for循环n次就行了。

方法二:

iterator beg = multimap.lower_bound(key); // 指向键 >= key 的第一个元素
iterator end = multimap.upper_bound(key); // 指向键 > key 的第一个元素
while(beg != end){
	cout << beg.second;
	beg++;
}

方法三:
equal_range()返回两个迭代器的pair,分别于方法二中的两个迭代器对应。

pair<iterator, iterator> pos = multimap.equal_range(key);
while(pos.first!= pos.second){
	cout << pos.first -> second;
	++pos.first;
}

2、删除
erase(key)将消除拥有该key的所有元素。

6.3 迭代器iterator

每种容器都定义了自己的迭代器类型。例如这句话定义了一个名为iter的变量,它的数据类型是由vector< int >定义的iterator类型。

std::vector<int>::iterator iter;

6.3.1 begin和end操作

1、begin()

由begin返回的迭代器指向第一个元素,即v[0]

std::vector<int>::iterator iter = v.begin();

2、end()

由end返回的迭代器指向末端元素的下一个位置,是不存在的元素,起哨兵的作用,表示已经处理完容器。

不要存储end()操作返回的迭代器。因为对容器进行增删之后,将导致存储的内容失效,因为end()已经改变。

6.3.2 自增和解引用运算

// 将iter指向的元素设为0
*iter = 0; 
// 将迭代器移动一个位置
iter++; 
iter->mem // = (*iter).mem

6.3.3 算术操作

仅适用于vector和deque容器

std::vector<int> values = { 1, 2, 5, 7 };
std::vector<int>::iterator mid = values.begin() + values.size() / 2;
std::cout << *mid << std::endl; 
// 输出 5
std::cout << *(mid+1) << std::endl; 
// 输出 7

6.3.4 示例: vector的迭代器

std::vector<int> values = { 1, 2, 5, 6 };
for (std::vector<int>::iterator iter = values.begin(); iter != values.end(); iter++) {
	std::cout << *iter << std::endl;
}
// 输出 1 2 5 6

6.3.5 const_iterator

对于const_iterator类型的迭代器,*iter是一个const,因此不能对其进行赋值操作,例如 *iter = 0;

因此const_iterator只能用于读取容器。

for (std::vector<int>::const_iterator iter = values.begin(); iter < values.end(); iter++) {
	*iter = 0; // 错误用法
}

6.3.6 示例: 哈希表的迭代器

三种方式都输出AA 2 BB 5

#include <iostream>
#include <string>
#include <unordered_map>

int main() {

    using Map = std::unordered_map<std::string, int>;
    Map map;
    map["AA"] = 2;
    map["BB"] = 5;

    // 1
    for (Map::const_iterator it = map.begin(); it != map.end(); it++) {
        std::cout << it->first << std::endl;
        std::cout << it->second << std::endl;
    }

    // 2
    for (auto keyValue : map) {
        std::cout << keyValue.first << std::endl;
        std::cout << keyValue.second << std::endl;
    }

    // 3 结构化绑定 C++17
    for (auto [key, value] : map) {
        std::cout << key << std::endl;
        std::cout << value<< std::endl;
    }

    std::cin.get();
}

6.4 标准库bitset类型

6.4.1 头文件

#include <bitset>

6.4.2 定义以及初始化

// 定义
std::bitset<5> b;

// 1、用unsigned值初始化
bitset<16> b1(0xffff);
bitset<32> b2(0xffff);

// 2、用string对象初始化
std::string str("1100");
bitset<32> b3(str);
// b3的第二位和第三位为1,其余30位为0,用string初始化bitset是反着的。

6.4.3 bitset对象的操作

1、b.any() 是否存在1的位?

2、b.count() 返回1的个数

3、b.test(i) 第i位是否为1?

4、b.size()

5、b.reset() 都置为0;b.set() 都置为1;b.set(i) 把第i位置为1

6、b.flip() 都取反

7、输出

std::bitset<5> b;
std::cout << b << std::endl; // 输出00000
std::bitset<16> b(1100);
std::cout << b << std::endl;
// 输出1100的二进制表示

unsigned long ans = b.to_ulong();
std::cout << ans << std::endl; 
// 输出1100

6.5 标准库array类型(静态数组)

6.5.1 C风格的数组

6.5.1.1 数组的初始化

C++提供了数组和指针,类似于vector和迭代器。

数组的长度是固定的,无法添加元素。

const unsigned array_size = 3;
int a[array_size] = { 1,2,3 };

// 显式初始化不需提供维数
int b[] = { 4,5,6,7 };
6.5.1.2 特殊的字符数组

这样的数组既可以用一系列char初始化,也可以用一个string初始化。

// string初始化时,需要一个额外的空字符用于结束字符串。
// ca1的长度可以初始化为3,ca3的长度至少要初始化为4
char ca1[] = { 'c','+','+' };
char ca2[] = { 'c','+','+','\0' };
char ca3[] = "c++";

std::cout << sizeof(ca1) / sizeof(char) << std::endl; // 输出 3
std::cout << sizeof(ca2) / sizeof(char) << std::endl; // 输出 4
std::cout << sizeof(ca3) / sizeof(char) << std::endl; // 输出 4
6.5.1.3 求数组长度
sizeof(数组名)/ sizeof(元素类型)
6.5.1.4 访问数组元素

通过下标访问

char ca3[5] = "c++";

std::cout << ca3[2] << std::endl;
// 输出 +

for (auto c : ca3) {
	std::cout << c;
}
// 输出 c++
6.5.1.5 指针与数组

1、数组名是指向数组第一个元素的指针。

int a[] = { 4,5,6,7 };
int* p = a;

std::cout << *p; // 输出 4
std::cout << *(p + 2); // 输出 6

int* p1 = &a[3];
std::cout << *p1; // 输出 7

2、指针偏移

int a[] = { 4,5,6,7 };
std::cout << *p; 
// 输出 4
std::cout << *(p + 2); 
// 输出 6,指针偏移了2*4 = 8
std::cout << *(int*) ((char*)p + 8); 
//输出 6

3、指针是数组的迭代器

const int size = 4;
int a[] = { 4,5,6,7 };
for (int *ptr = a, *end = ptr + size; ptr != end; ptr++) {
	std::cout << *ptr << ' ';
}
// 输出 4 5 6 7
6.5.1.6 栈和堆创建数组

两种方式都能创建数组,但生命周期不同。

1、栈上,scope结束时自动摧毁数组

int another[5]; 

2、堆上,需要手动删除。不用new就不用delete

// 无初始化
int *another = new int[5];
// 有初始化,都初始化为0
int *another = new int[5]();
delete[] another;

没有new却delete将报错。

int i = 0;
int* p = &i;
delete p; // 报错

6.5.2 C风格字符串

尽量用C++的标准库类型string

6.5.2.1 C风格字符串的使用

1、C风格字符串的定义

C风格字符串是以空字符null结束的字符数组。例如ca2和ca3,而ca1不是C风格字符串,因为它不带结束符null。

char ca1[] = { 'c','+','+' };
char ca2[] = { 'c','+','+','\0' };
char ca3[] = "c++";

2、空终止字符的使用

char Example[7] = { 'x', 'i', 'n', 'g', 'y', 'u', 0 };
//char Example[7] = { 'x', 'i', 'n', 'g', 'y', 'u', '\0'};
std::cout << Example << std::endl; // 输出 xingyu
char Example2[8] = "xi\0ngyu";
std::cout << Example2 << std::endl; 
// 输出 xi
std::cout << strlen(Example3) << std::endl; 
// 输出2

3、用指针来操控C风格字符串

C++通过(const) char*类型的指针来操控C风格字符串,这个指针变量本身是一个变量,用于存放字符串的首地址。

字符串存放在以该首地址为首的一块连续的内存空间中,并以‘\0’作为串的结束。

const char *example = "xingyu";
// 也可写作 char* Example = "xingyu";
// 如果你知道你自己不会修改此字符串,就加上const
std::cout << example << std::endl;
// 输出 xingyu
// example是一个指针,指向第一个字符,本应输出内存地址,但定义为输出指向内存的内容。

// 查看首字符x的地址
std::cout << (int*) example<< std::endl; 

std::cout << *example << std::endl; // 输出x
std::cout << *(example + 1) << std::endl; // 输出 i

4、可以换行的初始化方式

const char* Example2 = "hello\n"
"world\n"
"!\n";
//const char* Example2 = R"(hello
//world
//!)";
std::cout << Example2 << std::endl;
6.5.2.2 C风格字符串的标准库函数

1、头文件

#include <cstring>

它其实是C语言string.h的C++版本。

2、strlen(s)

char *b = { "hello" };
std::cout << strlen(b);
// 输出5,不包含结尾空结束符

3、
strncat(s1, s2, n) 将s2的前n个字符连接到s1后面,并返回s1
strncpy(s1, s2, n) 将s2的前n个字符复制给s1,并返回s1

4、动态数组的使用

const char* str1 = "success";
const char* str2 = "fail";
const char* tmp;
int situation = 2;
if (situation == 1) {
	tmp = str1;
}
else {
	tmp = str2;
}
int length = strlen(tmp) + 1;
char* output = new char[length];
strncpy(output, tmp, length);
std::cout << output;

6.5.3 标准库数组array

1、头文件

#include <array>

2、声明

长度不能改变,为常数。

std::array<int,5> Example; // 长度不能增长,为常数

3、赋值

Example[0] = 5;
Example[1] = 2;

4、输出元素

for (int index : Example) {
	std::cout << index << std::endl;
}

5、排序

std::sort(Example.begin(),Example.end());

6、求长度

std::cout << Example.size() << std::endl;
// 输出 5

6.5.4 多维数组

6.5.4.1 多维数组的初始化
// 只初始化了每行的第一个元素
int a[3][4] = { {0},{4},{8} };
std::cout << a[1][0];
// 输出 4
6.5.4.2 指针和多维数组

1、5*5的二维数组

#include <iostream>

int main() {

	//int* array = new int[5];
	int** a2d = new int* [5];
	// array[0]是一个int,a2d[0]是一个int指针
	for (int i = 0; i < 5; i++) {
		a2d[i] = new int[5];
	}
	
	// 释放内存过程:先删元素,再删保存指针的数组a2d
	for (int i = 0; i < 5; i++) {
		delete[] a2d[i];
	}
	delete[] a2d;
	
	std::cin.get();
}

2、5* 5* 5的三维数组

int*** a3d = new int** [5];
for (int i = 0; i < 5; i++) {
	a3d[i] = new int*[5];
	for (int j = 0; j < 5; j++) {
		a3d[i][j] = new int[5];
		// 等于
		//int** ptr = a3d[i];
		//ptr[j] = new int[5];
	}
}
//a3d[0][0][0] = 5;

6.8 泛型算法

#include <algorithm>
#include <numeric>

6.8.1 sort

std::vector<int> values = { 3,5,1,4,2 };
	
// 小到大排序
std::sort(values.begin(), values.end());

// 大到小排序
std::sort(values.begin(), values.end(),std::greater<int>());

6.8.2 stable_sort

sort是不稳定排序,基于指定的排序规则排序之后,具有相同顺序的多个元素的顺序可能是随机的;而stable_sort可以保证这些元素的相对次序与排序前不变。

// 特定排序规则:按字典序为默认规则,此处为按长度由低到高排序
bool isShorter(const std::string& s1, const std::string& s2) {
	return s1.size() < s2.size();
}

int main() {
	std::vector<std::string> values = { "a","cd","bcd","ab","dfsj" };
	std::stable_sort(values.begin(), values.end(), isShorter);
}

7 栈与堆

7.1 作用域 ( scope )

// 1
{
}

// 2
if(){
}

// 3 类作用域
class Entity{

}

7.2 创建并初始化对象

7.2.1 在栈上创建

基于栈存储的变量,作用域(scope)结束之后,栈会自动销毁,变量也就被自动销毁。

栈的空间通常比较小,如果类较大或者类的对象较多,要去堆上创建。

#include <iostream>
#include <string>

using String = std::string;

class Entity {
private:
	String Name;
public:
	Entity() : Name("Unknown") {}
	Entity(const String& name):Name(name) {}

	const String& GetName() const
	{
		return Name;
	}
};

int main() {
	Entity entity;
	std::cout << entity.GetName() << std::endl;
	Entity entity2("xingyu");
	// 效果等同 Entity entity2 = Entity("xingyu");
	std::cout << entity2.GetName() << std::endl;
	std::cin.get();
}

7.2.2 在堆上创建(new)

可以显式地控制对象的生存期。需要手动删除。

new关键字的作用:
1、在堆上找到一块足够大的内存,满足需求(例如Entity需要的内存),并返回给我们指向那块内存的指针。
2、调用构造函数。

Entity* entity = new Entity("xingyu");
// 返回这个entity在堆上被分配的内存的地址

std::cout << entity->GetName() << std::endl;

// 手动删除
delete entity;

7.2.3 replacement new

为其指定一块内存,而不需new关键字为我们寻找内存,仅需其起到调用构造函数的作用。

调用构造函数,在特定的地址初始化你的Entity。

int* b = new int[50];
Entity* e = new(b) Entity();

delete e;
delete[] b;

8 模板

例1:
只有在调用Print() 时,它才会被实际创建(用给定的模板参数)。

#include <iostream>
#include <string>

template<typename T>
void Print(T value) {
	std::cout << value << std::endl;
}

int main() {
	Print(5);
	// Print<int>(5);
	Print(5.5f);
	Print("hello!");

	std::cin.get();
}

例2:

template<int N>
class Array {
private:
	int m_Array[N];
public:
	int GetSize() const { return N; }
};


int main() {
	Array<5> array;
	std::cout<< array.GetSize()<<std::endl;
	std::cin.get();
}

// 实际上:
//class Array {
//private:
//	int m_Array[5];
//public:
//	int GetSize() const { return 5; }
};

例3:

template<typename T, int N>
class Array {
private:
	T m_Array[N];
public:
	int GetSize() const { return N; }
};


int main() {
	Array<int, 5> array;
	//Array<std::string, 5> array;
	std::cout<< array.GetSize()<<std::endl; // 输出5
	std::cin.get();
}

9 宏

#include <iostream>

#define W std::cin.get()

int main() {
	W;
}

1.4 VS 基本操作

1.4.1 格式化全部代码

Ctrl+A+K+F

1.4.2 批量注释代码

注释代码:Ctrl+K+C

反注释代码:Ctrl+K+U

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值