C++标准库兼容C语言标准函数库,可以在C++标准库中直接使用C语言标准函数库文件,同时C++标准库增加了自己的源代码文件,新增文件使用C++编写,多数代码放在std命名空间中,所以连接C++标准库文件后还需要 using namespace std;。
【终端输入输出】
C++标准库iostream文件提供终端输入输出功能,此文件同时兼容C语言标准函数库中的终端输入输出函数。
终端输出
cout对象用于终端输出,使用方式如下:
cout << 内容1 << 内容2 << 内容3 ......;
每个输出内容使用 << 符号连接,输出内容数量可变,char类型数据不会转换,其它数学数据会自动转换为字符串表示形式,若要输出换行则添加 << endl。
#include <iostream>
using namespace std;
int main()
{char ali[] = "阿狸";int age = 8;cout << "姓名:" << ali << endl << "年龄:" << age << "岁" << endl;return 0;
}
使用cout输出数学数据时,前缀0表示8进制,前缀0x表示16进制。
#include <iostream>
using namespace std;
int main()
{/* 输出20、16、32,多个输出内容可以换行存储 */cout << 20 << endl<< 020 << endl<< 0x20 << endl;return 0;
}
终端输入
cin对象用于终端输入,使用方式类似cout,输入符号为 >>。
#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对象之间赋值。
#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对象可以嵌套定义,从而定义多维数组:
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不会设置元素初始值。
#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,清空内部元素。
定义方式:
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}; //定义动态数组时赋值,数组元素个数由赋值数据个数确定。
#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对象并赋值的常用方式:
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对象进行修改、连接、比较。
#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 对象。
#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,与另一个栈整体交换数据。
#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,与另一个队列进行数据交换。
#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,清空队列。
#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,清空列表。
#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,退出此内存的管理权,并返回管理的内存地址,此函数不释放内存。
#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成员函数设置管理内存。
#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创建p2printf("%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对象管理内存时也会有死锁现象发生,比如下面的代码。
#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即可解决问题,但是此时其中一方对内存的使用会受到限制。
智能指针的作用是方便我们编写代码,而不是为了增加程序代码的复杂度,在混乱的智能指针关系中可以选择使用普通指针,内存管理完全由自己掌控,注重执行效率的程序也应该使用普通指针自行管理内存。