目录
3.1命名空间的using声明
格式:using 命名空间::名字;(别忘记末尾的分号)
该方法可略过命名空间名
#include<iostream>
using std::cin;
using std::cout;
using std::endl;
int main()
{
int v1;
cin>>v1;
cout<<v1<<endl;
}
注意:头文件不应该包含using声明,若头文件含有using声明,整个程序将共享该using声明,可能影响主程序。
3.2标准库类型string
前提:使用string必须先声明以下语句:
#include<iostream>
using std::string;
基本的string操作:
1.定义和初始化string对象
string s1;//s1为空字符串
string s2="hello";//用字面值初始化
string s3("hello");//等价于上条语句
string s4=s2;//用赋值符号拷贝初始化
string s5(s2);//等价于上条语句
string s6(10,'c');//直接初始化,s4为10个c
直接初始化:不适用等号,常使用小括号
拷贝初始化:利用赋值符号,即等号进行初始化
应用场景:对于单个初始值,用拷贝初始化;多个初始值,用直接初始化更方便
//单个初始值时
string s1="hello";
string s2=s1;//拷贝初始化
string s3("hello");//直接初始化
//多个初始值时
string s4(10,'c');//直接初始化
string s5=string(10,'c');//拷贝初始化
2.读写string对象
方法1:用基本IO操作读写
string s;
cin>>s;
cout<<s<<endl;
若输入" hello world",则输出为"hello"
原理:cin操作忽视第一个字符前的所有空白(包括空格符、换行符、制表符),从第一个真正的字符读起,直到遇到下一个空白结束读取
缺点:无法读取空白
string s1,s2;
cin>>s1>>s2;
cout<<s1<<s2<<endl;
若输入" hello world",则输出"helloworld"
读取未知数量的string对象
int main()
{
string word;
while(cin>>word)
{
cout<<word;
}
cout<<endl;
}
直到输入文件结束标记cirl+z或非法标记结束
方法2:用getline读取整行
getline代替>>运算符,参数为一个输入流和一个string对象。getline对象从给定的输入流中读入内容,直到遇到换行符为止(内容包括了换行符),再把读取的内容存到string对象里(不包括换行符)。getline一遇到换行符就读取并返回结果。
//不断读取每行内容,直到直到遇到文件终止符
int main()
{
string s;
while(getline(cin,s))
{
cout<<s<<endl;
}
}
//仅仅读取一行并输出
int main()
{
string s1;
getline(cin,s1);
cout<<s1<<endl;
}
3.string的操作
string str="Hello";
str[2]表示字符串"Hello"第3个字符l
3.1string的函数
empty函数:若string对象为空字符串,则返回为真
size函数:返回string对象内容的字符个数(包括空格,不包含字符串末尾的'\0')
int main()
{
while(getline(cin,s))
{
if(!s.empty())
{
cout<<s.size()<<endl;
}
}
}
注意:size函数返回类型是size_type,该类型是在类string中定义的,与机器无关。
size_type为无符号类型的值,不能与int放在一起,否则容易因为强制类型转换出错
//比较输入的两个字符串长度大小,并输出长度较大的字符串的长度
#include<iostream>
#include<string>
using std::cin;
using std::cout;
using std::endl;
using std::string;
string::size_type max(string s1,string s2)
{
return (s1.size()>s2.size())?s1.size():s2.size();
}
int main()
{
string s1,s2;
cin>>s1>>s2;
cout<<max(s1,s2)<<endl;
}
3.2比较string对象
用> = < >= <=比较,比较方法:从第一个字符开始比较,按ASCII码比,直到比出谁大;若A和B长度不同,A为abcde,B为abcdefg,则B大
3.3赋值
string s1(10,'c);
string s2=s1;
3.4相加
第一种:通过+把两string对象内容相连
string s1("Hello,"),s2("world.");
cout<<S1+s2<<endl;
结果为Hello,world.
第二种:通过+把字面值和string对象内容相连
注意:需要保证每个+两边至少有一个string对象
string s1="Hello",s2="world";
cout<<s1+','+s2+'\n'<<endl;//正确
cout<<"Hello"+','+s2+'\n';//错误
3.5处理string的字符
一、处理string字符的函数
二、处理单个字符
用下标运算符[ ],该符号内为size_type类型
注意判断[]内的数字是否大于0且小于字符串长度(若设为size_type类型,只需判断小于字符串长度)
三、处理部分字符
用下标运算符或迭代器
四、处理每个字符
可使用范围for语句
范围for语句格式
for(declaration:expression)
{
statement;
}
expression表示一个序列
declaration即声明一个变量,该变量用来访问 序列中的所有元素,每次迭代,declaration的变量都会被初始化为expression部分的下一个元素值
//数字符串中有多少个标点符号
int main()
{
string s;
unsigned int punct_cnt = 0;
cin >> s;
for (auto c : s)
{
if (ispunct(c))
{
punct_cnt++;
}
}
cout << punct_cnt << endl;
}
//把输入的字符串每个字符改为X
int main()
{
string s;
cin>>s;
for(auto &c:s)//auto&c可用来访问并改变s的每个字符,而auto c只能访问s的每个字符
{
c='X';
}
}
五、使用下标执行随机访问
//将0~15十进制数转换为十六进制并输出
int main()
{
string s = "0123456789ABCDEF";
string result;
decltype(s.size()) n;
cout << "请随意输入0~15的十进制数字:" << endl;
while (cin >> n)
{
if (n < s.size())
result += s[n];
else
{
cout << "输入有误,请重新输入" << endl;
}
}
cout << "输入的十进制数字转换为十六进制结果为:" << result << endl;
}
3.3vector
使用vector前提
#include<vector>
using std::vector;
vector为模板类型容器,容纳对象(不能是引用),要指定对象类型完成实例化
可以套娃,即容器的元素仍为容器
vector v;//错误
vector<int> v;//正确
vector<vector<int>> vv;//正确
3.3.1定义和初始化vector对象
vector<T> v1;//创建空容器
vector<T> v2(v1);//v1拷贝到v2
vector<T> v2=v1;//v1拷贝到v2
vector<T> v3(n,val);//创建n个值为val元素的容器
vector<T> v4(n);//创建n个默认初始化元素的容器
vector<T> v5{a,b,c};//创建包含3个元素且值分别被初始化为a,b,c的容器
vector<T> v6={a,b,c};//创建包含3个元素且值分别被初始化为a,b,c的容器
注意:使用拷贝初始化时,一定要保证元素类型相同
vector<string> v7=v3;//错误,元素类型不同
v3和v4通过指定元素数量来创建对象,若只是像v3一样说明元素个数,则创建的元素对象将被默认初始化 :string被默认初始化为空字符串,int则为0等等。
值得注意的是:有些类要求明确提供初始值,不能像v3那样初始化
v5和v6初始化方法为列表初始化,对于多个值的初始化,一般采用花括号列表初始化
vector<T> v={a,b,c};//正确
vector<T> v=(a,b,c);//错误
注意区分花括号和圆括号的使用:
vector<int> v1(10);//圆括号,创建10个对象,值为0
vector<int> v2{10};//花括号,创建一个对象,值为10
vector<int> v3(10,1);//圆括号,创建10个对象,值为1
vector<int> v4{10,1};//花括号,创建2个对象,值分别为10和1
vector<string> v5{"hi"};//花括号,一个对象,值为hi
vector<string> v6("hi");//圆括号,错误,不能用字面值构建vector对象
vector<string> v7{10};//花括号,10不是字符串,识别为元素数量,故创建10个对象,均为空字符串
vector<string> v8{10,"hi"};//花括号,10不是字符串,识别为元素数量,故创建10个对象,均为hi
3.3.2vector基本操作
vector<int> v;
int n;
while(cin>>n)
{
v.push_back(n);
}
可以使用范围for语句,但必须注意不能在for内添加或删减元素导致容器大小改变
使用下标访问时要注意索引是否合法(大于等于0,小于元素数量),不能用下标访问空容器
#include<iostream>
#include<vector>
#include<string>
#include<cctype>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main()
{
vector<string> v;
string s;
while (cin >> s)
{
v.push_back(s);
}
for (auto&s : v)
{
for (auto&c : s)
{
c=toupper(c);
}
}
for (auto s : v)
{
cout << s << endl;
}
}
迭代器
迭代器可视作访问容器的指针,基本上每个容器都能用(但不是每个容器都能用下标)
迭代器类型:iterator和const_iterator
iterator可读可写,const_iterator不可修改所指向的对象,但可以修改迭代器本身
vector<int>::iterator it;
string::iterator it2;
vector<int>::const_iterator it3;
string::const_iterator it4;
获取迭代器的函数
对于vector有成员函数begin()和end(),begin函数指向vector容器第一个对象,end函数指向容器尾元素的下一个位置,返回iterator类型迭代器
特别地,还有cbegin()和cend(),即返回const_iterator类型迭代器
#include<iostream>
#include<vector>
using std::vector;
vector<int> v;
auto beg=v.begin();
auto end=v.end();
//检查容器非空
if(v.begin()!=v.end())
{
cout<<"容器非空"<<endl;
}
迭代器操作
迭代器运算
注意两个迭代器相减得到的值数据类型为difference_type的带符号整型数,该数和int类型相加减后,会得到结果为int类型
典例:二分搜索(迭代器应用)
#include<iostream>
#include<cctype>
#include<vector>
using std::endl;
using std::cout;
using std::cin;
using std::vector;
using std::cerr;
int main()
{
vector<int> v(10);
int i = 1;
int sought = 0;
for (auto& c : v)
{
c = i;
i++;
}
auto beg = v.begin();
auto end = v.end();
auto mid = beg + v.size() / 2;
cout << "请输入要查找的值" << endl;
cin >> sought;
if (sought > 10)
{
cerr << "输入有误" << endl;
return -1;
}
while (mid != end && *mid != sought)
{
if (*mid > sought)
{
end = mid;
}
else
{
beg = mid + 1;
}
mid= beg + (end-beg) / 2;
}
if (*mid == sought)
{
cout << "找到了" << endl;
}
else
{
cout << "没有这个值" << endl;
}
}
3.5数组
数组是内置类型
定义和初始化
数组不同于string和vector,初始化时必须用常量声明数组大小,或者直接列表初始化
初始值不能超过数组大小
int a1[10];//正确
int a2[]={1,2,3,4,5,6,7,8,9,10};//正确
int a3[10]={1,2,3,4,5,6,7,8,9,10};//正确,等价于上条语句
string a4[3]={"hi","bey"};//正确,等价于a4[]={"hi","bey",""}
unsigned int cnt=42;
int b[cnt];//错误,cnt不是常量
constexpr unsigned int sz=42;
int b[sz];//正确
特别地,对于字符数组,可以用字符串字面值赋值,但一定要注意包含字符串末尾的空字符
char c[]={'C','+','+','\0'};
char c[4]="C++";
char c[]="C++";//等价于上条语句
数组与数组之间不能直接拷贝或赋值
数组与指针或引用的定义和初始化:读法为从内到外
int* p1[10]; //正确,创建了一个数组,有10个以指向int类型的指针为元素
int& r1[10]=/*?*/; //错误,数组的对象不能为引用
int(*p2)[10]; //正确,创建了一个指针,指向有10个int元素的数组
int(&r2)[10]; //正确,创建了一个引用,指向有10个int元素的数组
可用下标访问数组
指针和数组
数组名是指针,指向数组首个元素
auto和decltype对于数组名得到的结果不同,前者得到指针,后者得到数组
int a[3]={1,2,3};
auto p=a;//p为指针,指向数组a的首个元素
decltype(a) ia;//ia为由3个元素构成的数组
p=42;//错误,p为指针
ia=p;//错误,ia为数组
p=ia+2;//正确
ia[3]=3;//正确
获取数组首元素地址和尾元素下一位置地址的方法:
1.通过直接使用数组名获取获取数组首元素地址
2.通过使用标准库函数begin和end
#include<iterator>
#include<iostream>
using std::begin;
using std::end;
int main()
{
int ia[] = { 1,2,3 };
int* beg = begin(ia);
int* last = end(ia);
}
指针的使用和运算与迭代器相同,不同的是指针相减得到的值类型为ptrdiff_t,迭代器相减为difference_type,均为带符号整型数,不需死记,只需了解。
若指针指向不相关(不是同一数组)对象,无法比较指针
注意解引用的优先级,两者意义不同
n=*(ia+4);
n=*ia+4;
指针和下标:
只要指针指向首元素或尾元素下一位置这一范围内的元素,包括首元素或尾元素下一位置的元素
int a[]={1,2,3};
int *p=a+1;
int n1=p[1];//n1为a[3]
int n2=*(p+1);//等价于上条语句
int n3=p[-1];//n3为a[0]
C风格字符串
指以字符数组存放空字符‘\0'结尾的字符串
标准库与旧接口
1.vector能用数组初始化,只需指明所利用(初始化)的数组的初元素位置和尾元素下一个的位置
int a[]={1,2,3);
vector<int> va(std::begin(a),std::end(a));
vector<int> va2(a+1,std::end(a));
2.string能用字符串字面值初始化或赋值
string能用以空字符结尾的字符数组初始化或赋值
#include<iterator>
#include<iostream>
#include<string>
using namespace std;
int main()
{
char a[] = "C++";
string s = a;
cout << s << endl;
}
相反地,不能用string初始化或赋值字符数组
string有成员函数c_str()可返回指向常量的指针(const char*),该指针指向一个以空字符结尾的字符数组,其他内容与string对象完全一样
但要注意:若在这之后改变了s的值,该返回的数组可能无效
#include<iterator>
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s = "C++";
const char* p = s.c_str();
}
3. string对象加法运算中允许以空字符结尾的字符数组作为右侧的运算对象
多维数组
即数组的数组
定义和初始化
int a1[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};
int a2[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
//a1与a2等价
int b[3][4]={{0,1,2},{3,4,5}};
//第一行0120,第二行3450,每个花括号表示一行
下标引用
a1[0]表示a1的第一个元素,即一个有4个元素的int数组
a1[0][0]表示a1的一个元素,即一个有4个元素的int数组取第一个元素
指针和多维数组&使用范围for语句处理多维数组
数组名为指针,指向该数组的第一个元素
对于多维数组,第一个元素为内层数组,所以多维数组的数组名为指针,且指向该数组的第一个元素,即第一个内层数组
int* p1[4];//p为指针数组,含4个元素int*元素的数组
int a[3][4];
int(*p2)[4]=a;//表示p为指针,指向含4个int元素的数组a[0]
p2=&a[2];//表示p为指针,指向含4个int元素的数组a[2]
用传统for循环输出元素
int a[3][4];
for (size_t i = 0; i != 3; i++)
{
for (size_t j = 0; j != 4; j++)
{
a[i][j] = i * 4 + j;
}
}
for (auto& row : a)
{
for (auto col : row)
{
cout << col << endl;
}
}
注意:若为如下所示,最外层for写成(auto row:a),则row将会被定义为指针类型(int*),获取每个元素的数组名,从而指向a的每个元素里面(有4个int元素的数组)的首元素,此时内层循环不合法,导致无法通过编译。
解决方法如上所示,写成(auto&row:a),改成引用,就能将指针变为数组
因此,对于多维数组的范围for,只有内层不需要引用,其他外层都需要使用引用
for (auto row : a)
{
for (auto col : row)
{
cout << col << endl;
}
}
区分传统for语句和范围for语句(重点!易混!)
两者的区别在于:范围for语句中变量类型的判断是根据给出的序列的元素,而不是序列本身
传统for语句:
第一个for,p的类型为int*[4],即为指针,指向a的第一个元素,即内层数组
第二个for,*p为a的第一个元素,即内层数组 ,而表示该内层数组的方式是数组名,所以*p被转换为指向内层数组第一个元素的指针,所以q的类型为int*
因此,*q的意思是第一个内层数组中的元素
范围for语句:
第一个for,a的元素为类型为int[4],即数组,表示为数组名,被转换为指针,指向内层数组的第一个元素,故该指针类型为int*,即row的类型为int*
第二个for,由于指针类型没有元素,因此无法遍历,所以第二个for无法编译
因此第一个for中为了不让数组转换为数组名进而转换为指针,需要使用引用
//传统for语句
for (auto p = a; p != a + 3; p++)
{
for (auto q = *p; q != *p + 4; q++)
{
cout << *q << endl;
}
}
//范围for语句(错误举例)
for (auto row : a)//应改为(auto &row:a)
{
for (auto col : row)
{
cout << col << endl;
}
}
类型命名简化多维数组的指针
using int_array=int[4];
typedef int int_array[4];
具体应用见以下典例末尾处
典例
编写3个不同版本的程序,令其均能输出ia的元素。版本1使用范围for语句管理迭代过程;版本2和版本3都使用普通for语句,其中版本2要求使用下标运算符,版本3要求使用指针。此外,在所有3个版本的程序中都要直接写出数据类型,而不能使用类型别名、auto关键字和decltype关键字。
#include<iostream>
#include<cstddef>
#include<iterator>
using std::cout;
using std::endl;
using std::begin;
using std::end;
int main()
{
constexpr size_t ROW = 3, COL = 4;
int ia[ROW][COL];
for (size_t i = 0; i != ROW; i++)
{
for (size_t j = 0; j != COL; j++)
{
ia[i][j] = i * COL + j;
}
}
//第一种方法,范围for(用auto)
cout <<"第一种方法1"<<endl;
for (auto& r : ia)
{
for (auto c : r)
{
cout << c << " ";
}
}
cout << endl;
//第一种方法,范围for(用decltype)
cout << "第一种方法2" << endl;
for (decltype(*ia) r : ia)
{
for (int c : r)
{
cout << c << " ";
}
}
cout << endl;
//第一种方法,范围for(不用auto,decltype)
cout << "第一种方法3" << endl;
for (int (&r)[4] : ia)
{
for (int c : r)
{
cout << c << " ";
}
}
cout << endl;
cout << "第二种方法" << endl;
//第二种方法,用下标
for (size_t i = 0; i != ROW; i++)
{
for (size_t j = 0; j != COL; j++)
{
cout << ia[i][j] << " ";
}
}
cout << endl;
cout << "第三种方法1" << endl;
//第三种方法,指针(用auto)
for (auto p = ia; p != ia + ROW; p++)
{
for (auto q = *p; q != *p + COL; q++)
{
cout << *q << " ";
}
}
cout << endl;
cout << "第三种方法2" << endl;
//第三种方法,指针(不用auto)
for (int(*p)[4] = ia; p != ia + ROW; p++)
{
for (int* q = *p; q != *p + COL; q++)
{
cout << *q << " ";
}
}
cout << endl;
//或者
for (int(*p)[4] = begin(ia); p != end(ia); p++)
{
for (int* q = begin(*p); q != end(*p); q++)
{
cout << *q << " ";
}
}
//使用类型别名
cout << endl;
cout << "使用类型别名" << endl;
using int_array = int[4];
for (int_array*p = begin(ia); p != end(ia); p++)
{
for (int* q = begin(*p); q != end(*p); q++)
{
cout << *q << " ";
}
}
cout << endl;
//或者
typedef int int_array[4];
for (int_array* p = begin(ia); p != end(ia); p++)
{
for (int* q = begin(*p); q != end(*p); q++)
{
cout << *q << " ";
}
}
}
小总结/补充
string和vector的下标索引类型为size_type,数组的下标索引类型为size_t(使用时需引入cstddef头文件),可用auto解决
两指针相减得到结果为ptrdiff_t标准库类型,定义在cstddef头文件中
两迭代器相减得到difference_type
数组使用的begin和end函数定义在iterator头文件中,并且为std::begin(数组名),std::end(数组名)
decltype返回数组类型
auto返回指针类型
int ia[]={1,2,3,4,5,6,7,8,9,10};
auto ia2(ia);//ia为int*类型,等价于auto ia2=&ia[0]
decltype(ia) ia3={0,1,2,3,4,5,6,7,8,9};//ia3为10个整数构成的数组