16. C++标准库

C++标准库兼容C语言标准函数库,可以在C++标准库中直接使用C语言标准函数库文件,同时C++标准库增加了自己的源代码文件,新增文件使用C++编写,多数代码放在std命名空间中,所以连接C++标准库文件后还需要 using namespace std;。

【终端输入输出】

C++标准库iostream文件提供终端输入输出功能,此文件同时兼容C语言标准函数库中的终端输入输出函数。

终端输出

cout对象用于终端输出,使用方式如下:

cpp 复制代码
cout << 内容1 << 内容2 << 内容3 ......;

每个输出内容使用 << 符号连接,输出内容数量可变,char类型数据不会转换,其它数学数据会自动转换为字符串表示形式,若要输出换行则添加 << endl。

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
	char ali[] = "阿狸";
	int age = 8;
	
	cout << "姓名:" << ali  << endl << "年龄:" << age << "岁" << endl;
	
	return 0;
}

使用cout输出数学数据时,前缀0表示8进制,前缀0x表示16进制。

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
    /* 输出20、16、32,多个输出内容可以换行存储 */
    cout << 20 << endl
    << 020 << endl
    << 0x20 << endl;
    
    return 0;
}

终端输入

cin对象用于终端输入,使用方式类似cout,输入符号为 >>。

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
    int a,b;
    cout << "请输入两个整数" << endl;
    cin >> a >> b;
    cout << "两个数据相加结果为:" << a+b << endl;
    
    return 0;
}

【STL】

C++标准库最大的特点是增加了数据结构相关类,这组类称为标准模板库,英文简称STL,内部代码大量使用了虚拟类型、符号函数。

数组

array 数组

array内部定义了一个私有数组和一些操作数组函数,常用成员函数如下:

at,使用下标调用数组元素,at会进行数组越界访问检查,越界访问会引发异常并退出程序。

size,返回数组元素个数。

data,返回数组第一个元素的指针。

swap,与另一个array数组进行元素交换,两个数组的元素都将改变。

fill,为所有元素设置一个初始值。

另外array有很多符号成员函数,可以使用[]符号访问内部元素([]符号函数不进行数组越界访问检查),使用=符号在两个长度相同的array对象之间赋值。

cpp 复制代码
#include <iostream>
#include <array>       //需要连接array文件
using namespace std;
int main()
{
    array<int, 5> a = {1,2,3,4,5};    //定义int类型数组,包含5个元素
    
    for(int i = 0; i < a.size(); i++)
    {
        printf("%d\n", a[i]);
    }
    
    return 0;
}

array对象可以嵌套定义,从而定义多维数组:

cpp 复制代码
array<array<int, 2>, 3> a = {{{1, 2}, {3, 4}, {5, 6}}};

赋值时需要使用三层大括号,最外层{}表示为对象a赋值,中层{}表示为内部二维数组赋值,内层{}表示为一维数组赋值。

valarray 数组

valarray数组新增了很多了处理数学数据的成员函数,同时array有的函数valarray基本也有。

sum,返回所有元素相加后的值。

max,返回值最大的元素。

min,返回值最小的元素。

常用符号函数如下:

operator+,为数组中每个元素加上指定数值,增加的数值可以使用另一个valarray指定,此时将同位置的元素相加。

operator-,同上,减去指定数值。

operator*,同上,乘以指定数值。

operator/,同上,除以指定数值。

另外与array的使用方式有一些区别:

1.valarray对象使用构造函数指定数组元素个数,或者创建对象时直接为数组赋值,根据赋值数据个数确定元素数量。

2.创建对象未赋值时,所有元素设置初始值为0,而array不会设置元素初始值。

cpp 复制代码
#include <iostream>
#include <valarray>
using namespace std;
int main()
{
    valarray<int> a = {1,2,3,4,5};
    valarray<int> b = {6,7,8,9,10};
    
    a = a + b;
    
    for(int i = 0; i < a.size(); i++)
    {
        printf("%d\n", a[i]);
    }
    
    return 0;
}

动态数组

动态数组是长度可变的数组,通过new申请内存实现长度可变,vector类提供动态数组功能,常用成员函数如下:

at,使用下标调用数组元素,同时进行越界访问检查。

size,返回数组元素个数。

data,返回数组第一个元素的指针。

swap,与另一个vector对象进行元素交换。

empty,判断数组是否为空并返回布尔值,为空返回1,否则返回0。

push_back,在末尾添加一个元素。

pop_back,在末尾删除一个元素。

front,返回数组第一个元素。

back,返回数组最后一个元素。

end,提取数组末尾元素的指针,并将指针+1再返回,可以与insert函数配合使用。

insert,在数组指定位置插入数据(位置使用指针指定),此位置及之后的数据向后移动,此函数为重载函数。

erase,删除数组指定位置的数据(位置使用指针指定),此位置之后的数据向前移动,此函数为重载函数。

clear,清空内部元素。

定义方式:

cpp 复制代码
vector<int> a;                //定义int类型动态数组。
vector<int> a(5);             //使用构造函数指定数组元素个数,元素初始值为0。
vector<int> a(5, 1);          //使用构造函数指定数组元素个数、元素初始值,这里创建5个元素的数组,初始值设置为1。
vector<string> a(5, "阿狸");   //使用string对象作为数组元素。
vector<int> a = {1,2,3,4,5};  //定义动态数组时赋值,数组元素个数由赋值数据个数确定。
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> a = {1,2,3,4,5};
	
	/* 末尾添加一个元素 */
	a.push_back(6);
	printf("末尾元素:%d\n", a.back());
	
	/* 末尾删除一个元素 */
	a.pop_back();
	printf("末尾元素:%d\n", a.back());
	
	int b[5] = {6,7,8,9,10};
	
	/* 添加一组元素,参数1指定插入位置指针,参数2指定提供元素的数组起始地址,参数3指定提供元素的数组末尾地址 */
	a.insert(a.end(), &b[0], &b[5]);
	for(int i = 0; i < a.size(); i++)
	{
		printf("%d\n", a[i]);
	}
	
	return 0;
}

字符串

string类实现字符串功能,其按照单字节的方式管理字符,双字节编码的字符可以使用wstring类。

创建string对象并赋值的常用方式:

cpp 复制代码
string a = "阿狸";   //使用=符号赋值。
string a("阿狸");    //使用构造函数赋值,参数为字符串指针。
string a(b);        //使用构造函数赋值,参数为另一个string对象。

更多重载构造函数使用方式参考C++标准库手册。

常用成员函数如下:

at,使用下标调用数组元素,同时进行越界访问检查。

swap,与另一个string对象进行元素交换。

empty,判断字符串是否为空,返回布尔值,为空返回true,否则返回false。

size,返回字符串有效字符的个数,不包含末尾空字符。

length,同size。

data,返回第一个元素的指针。

c_str,同data。

front,返回第一个有效字符。

back,返回最后一个有效字符。

insert,在指定位置插入一组元素。

clear,清空字符串。

string还定义了很多成员符号函数,可以使用这些符号函数进行与符号含义相同的操作,比如使用 =、+、== 对string对象进行修改、连接、比较。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
int main()
{
	string ali = "阿狸";
	string xyy = "喜羊羊";
	string zoo = ali + xyy;
	printf("%s\n", zoo.data());
	
	if(zoo == "阿狸喜羊羊")
	{
		printf("相同\n");
	}
	else
	{
		printf("不同\n");
	}
	
	return 0;
}

转换 string 对象的全局函数

stoi,将表示数学数据的字符串转换为对应的 int 类型数据,并返回。

stol,同上,转换为 long 类型。

stoul,同上,转换为 unsigned long 类型。

stoll,同上,转换为 long long 类型。

stoull,同上,转换为 unsigned long long 类型。

stof,同上,转换为 float 类型。

stod,同上,转换为 double 类型。

to_string,将一个数学数据转换为 string 对象表示形式。

to_wstring,同上,转换为 wstring 对象。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
int main()
{
    string s1 = "10";
    int i1 = stoi(s1);
    
    int i2 = 100;
    string s2 = to_string(i2);
    
    return 0;
}

stack类以栈的方式管理数据,栈的特点是最后存储的数据最先使用,类似弹夹,常用函数如下:

push,入栈一个数据。

top,返回栈顶数据,数据不从栈内删除。

pop,删除栈顶数据,数据不返回。

size,返回栈内数据个数。

empty,判断栈是否为空,返回布尔值,为空返回true,否则返回false。

swap,与另一个栈整体交换数据。

cpp 复制代码
#include <iostream>
#include <stack>
using namespace std;
int main()
{
    stack <int> a;
    
    for(int i = 0; i < 10; i++)
    {
        a.push(i);
    }
    
    while(!a.empty())
    {
        printf("%d\n",a.top());
        a.pop();
    }
    
    return 0;
}

队列

queue类提供队列方式管理数据的功能,队列管理方式是首先存储的数据首先使用,就像排队一样,数据的使用遵守先来后到的原则,数据不支持使用下标的方式调用,常用函数如下:

push,入队一个数据。

front,返回队列第一个数据,数据不删除。

pop,删除队列第一个数据,数据不返回。

back,返回队列最后一个数据,数据不删除。

size,返回队列内数据个数。

empty,判断队列是否为空,返回布尔值,为空返回true,否则返回false。

swap,与另一个队列进行数据交换。

cpp 复制代码
#include <iostream>
#include <queue>
using namespace std;
int main()
{
    queue <int> a;
    
    for(int i = 0; i < 10; i++)
    {
        a.push(i);
    }
    
    while(!a.empty())
    {
        printf("%d\n",a.front());
        a.pop();
    }
    
    return 0;
}

双端队列

双端队列可以在头部、尾部新增数据,使用数据时也可以在头部、尾部使用,还可以在中间添加、删除数据,数据的管理方式更灵活。

deque类实现双端队列功能,常用函数如下:

push_front,在头部添加一个数据。

push_back,在末尾添加一个数据。

front,返回头部第一个数据,数据不删除。

back,返回末尾第一个数据,数据不删除。

pop_front,删除头部第一个数据,数据不返回。

pop_back,删除末尾第一个数据,数据不返回。

at,使用下标访问队列元素,并进行越界访问检查。

size,返回队列内数据个数。

empty,判断队列是否为空,返回布尔值,为空返回true,否则返回false。

begin,返回头部第一个数据的指针。

end,返回末尾第一个数据的指针+1,可以与insert函数配合使用。

insert,在指定位置插入数据(位置使用指针指定),此位置及之后的数据向后移动,此函数为重载函数。

erase,删除指定位置的数据(位置使用指针指定),此位置之后的数据向前移动,此函数为重载函数。

swap,与另一个双端队列进行数据交换。

clear,清空队列。

cpp 复制代码
#include <iostream>
#include <deque>
using namespace std;
int main()
{
    deque <int> a;
    
    for(int i = 0; i < 10; i++)
    {
        a.push_back(i);
    }
    
    a.insert(a.begin()+3, 99);    //在下标3处添加数据
    a.erase(a.end()-2);           //在末尾下标-1处删除数据
    
    while(!a.empty())
    {
        printf("%d\n",a.front());
        a.pop_front();
    }
    
    return 0;
}

列表

列表类似双端队列,但是实现原理不同,在列表中间插入数据时无需移动其它数据,缺点是不能随机访问列表中间的数据,只能在头部或尾部访问数据。

list类实现列表功能,常用成员函数如下:

push_front,在头部添加一个数据。

push_back,在末尾添加一个数据。

front,返回头部第一个数据,数据不删除。

back,返回末尾第一个数据,数据不删除。

pop_front,删除头部第一个数据,数据不返回。

pop_back,删除末尾第一个数据,数据不返回。

size,返回列表的长度。

empty,判断列表是否为空,返回布尔值,为空返回true,否则返回false。

begin,返回头部第一个数据的指针,返回值为指针常量,不能修改。

end,返回末尾第一个数据的指针+1,返回值为指针常量,不能修改。

insert,在指定位置插入数据。

erase,在指定位置删除数据。

splice,将另一个列表插入到此列表的指定位置。

remove,删除指定值的所有数据。

swap,与另一个列表进行数据交换。

clear,清空列表。

cpp 复制代码
#include <iostream>
#include <list>
using namespace std;
int main()
{
	list <int> a = {1,2,3};
	
	list <int> b = {11,12,13};
	a.splice(a.end(), b);
	
	while(!a.empty())
	{
		printf("%d\n",a.front());
		a.pop_front();
	}
	
	return 0;
}

【智能指针】

C++使用new、delete简化了申请和释放内存的步骤,但是在代码非常复杂时依然很容易忘记释放内存,或者程序代码编写有问题导致不会执行delete,在大量申请内存时,内存使用完后不释放是很危险的,为了避免内存不被释放,C++增加了智能指针对象,智能指针可以自动管理申请的内存,其构造函数接收管理的内存地址,析构函数释放管理的内存。

智能指针定义在 memory 文件中,有如下三种:

unique_ptr,独占申请内存的管理权,不能与其它智能指针共同管理同一块内存,否则会出错。

shared_ptr,可以与其它shared_ptr对象共同管理同一块内存,内存不再使用后只被其中一个shared_ptr对象释放。

weak_ptr,在某些特殊情况下配合shared_ptr使用。

unique_ptr

常用成员函数如下:

get,返回管理的内存地址。

reset,修改管理的内存地址,原有内存会被释放,若修改的内存地址与原有内存地址相同,则内存也会被释放,reset不会判断修改值与原值是否相同。此函数为重载函数,若参数为空则用于释放内存。

release,退出此内存的管理权,并返回管理的内存地址,此函数不释放内存。

cpp 复制代码
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
    unique_ptr <string> p1(new string);    //<>符号内设置指针类型,p1为智能指针对象名称
    unique_ptr <string> p2;
    
    *p1 = "阿狸";
    p2 = move(p1);    //转移内存管理权,注意不能使用=符号直接在两个unique_ptr对象之间赋值
    
    printf("%s\n", p2->data());
    
    return 0;
}

shared_ptr

多个shared_ptr对象可以同时管理同一块申请的内存,多个shared_ptr对象会使用一个数据记录共同管理内存的智能指针数量,在只剩下一个shared_ptr对象后才会去释放内存,从而防止内存被提前释放或多次释放。

注意,多个shared_ptr对象共同管理内存时并非直接将此内存的地址交由shared_ptr对象,而是在创建第一个shared_ptr对象时接收管理内存,新的shared_ptr对象引用其它已赋值shared_ptr对象进行赋值。

常用成员函数如下:

get,返回管理的内存地址。

reset,修改管理的内存地址,使用方式同unique_ptr。

use_count,查询并返回管理内存的shared_ptr对象数量,查询结果包含本对象。

unique,判断本对象是否为内存的唯一管理者,返回布尔值,是的话返回true,否则返回false。

shared_ptr对象设置管理内存的方式如下:

1.使用构造函数直接设置管理内存。

2.使用构造函数调用另一个shared_ptr对象设置管理内存。

3.创建空对象后使用reset成员函数设置管理内存。

cpp 复制代码
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
	shared_ptr<string> p1(new string);
	*p1 = "阿狸";
	printf("%s\n"
		"内存管理者数量:%d\n",
		p1->data(), p1.use_count());
	
	shared_ptr<string> p2(p1);    //引用p1创建p2
	printf("%s\n"
		"内存管理者数量:%d\n",
		p2->data(), p1.use_count());
	
	return 0;
}

weak_ptr

weak_ptr用于配合shared_ptr使用,有如下两种作用:

1.创建一个只使用内存(有限使用)、不释放内存的智能指针,释放内存的工作交给其它shared_ptr对象执行。

2.解决shared_ptr对象的死锁问题,当两个对象都使用new申请内存存储、并分别使用一个shared_ptr成员管理对方时会形成死锁,导致两者都无法释放内存,此时可以将一个类中的shared_ptr成员改为weak_ptr。

使用注意事项:

1.weak_ptr对象不能操作内存中的数据,weak_ptr对内存的使用只限于查询此内存的相关属性,若需要使用此内存中的数据可以创建一个shared_ptr对象并使用其构造函数接收weak_ptr对象。

2.weak_ptr对象赋值使用的内存地址时只能调用其它shared_ptr对象赋值,不能直接接收一个内存地址,可以使用构造函数赋值、或者使用=符号函数赋值。

常用成员函数如下:

use_count,查询并返回管理此内存的shared_ptr对象数量,其中不包含本对象,weak_ptr对象没有内存管理权。

expired,查询并返回此内存是否有管理者,有则返回true,没有返回false。

reset,放弃内存的使用权,不再使用此内存。

死锁是一个计算机术语,表示两个行为互相制约,彼此都不能继续执行,好比你出门忘记带钥匙,需要开锁公司进行开锁,开锁公司开锁的前提是你提供房屋所有权证明,而你的证明文件锁在屋内,只有开锁之后才能出示所有权证明,此时开锁与出示所有权证明两个行为互相制约,在计算机中常见的情况是两个程序都需要独占两个文件使用权,程序1打开了文件1,程序2打开了文件2,此时两个程序都在等待对方关闭文件,从而形成死锁,使用shared_ptr对象管理内存时也会有死锁现象发生,比如下面的代码。

cpp 复制代码
#include <iostream>
#include <memory>
using namespace std;

class sheep;

class fox
{
public:
	fox()
	{
		printf("fox构造函数\n");
	}
	~fox()
	{
		printf("fox析构函数\n");
	}
	
	shared_ptr<sheep> p1;
	
	//......
};

class sheep
{
public:
	sheep()
	{
		printf("sheep构造函数\n");
	}
	~sheep()
	{
		printf("sheep析构函数\n");
	}
	
	shared_ptr<fox> p1;
	
	//......
};

int main()
{
	shared_ptr<fox> ali(new fox);
	shared_ptr<sheep> xyy(new sheep);
	
	ali->p1 = xyy;
	xyy->p1 = ali;    //两个对象互相使用shared_ptr指向对方
	
	return 0;
}

两个对象内部的shared_ptr智能指针成员互相指向对方,彼此都在等待对方被释放,从而造成两者都不会释放,两个对象的构造函数都不会执行,将fox或sheep的shared_ptr对象成员改为weak_ptr即可解决问题,但是此时其中一方对内存的使用会受到限制。

智能指针的作用是方便我们编写代码,而不是为了增加程序代码的复杂度,在混乱的智能指针关系中可以选择使用普通指针,内存管理完全由自己掌控,注重执行效率的程序也应该使用普通指针自行管理内存。

相关推荐
冷白白14 分钟前
【C++】C++对象初探及友元
c语言·开发语言·c++·算法
睡觉然后上课33 分钟前
c基础面试题
c语言·开发语言·c++·面试
qing_04060340 分钟前
C++——继承
开发语言·c++·继承
ya888g41 分钟前
GESP C++四级样题卷
java·c++·算法
小叶学C++1 小时前
【C++】类与对象(下)
java·开发语言·c++
NuyoahC1 小时前
算法笔记(十一)——优先级队列(堆)
c++·笔记·算法·优先级队列
FL16238631292 小时前
[C++]使用纯opencv部署yolov11-pose姿态估计onnx模型
c++·opencv·yolo
sukalot2 小时前
windows C++-使用任务和 XML HTTP 请求进行连接(一)
c++·windows
ぃ扶摇ぅ2 小时前
Windows系统编程(三)进程与线程二
c++·windows
Mr.Z.4113 小时前
【历年CSP-S复赛第一题】暴力解法与正解合集(2019-2022)
c++