<C++primer> 学习笔记【第三章】

目录

3.1命名空间的using声明

3.2标准库类型string

1.定义和初始化string对象

2.读写string对象

3.string的操作

3.1string的函数

3.2比较string对象

3.3赋值

3.4相加

3.5处理string的字符

3.3vector

3.3.1定义和初始化vector对象

3.3.2vector基本操作 

迭代器

 获取迭代器的函数

迭代器操作

 迭代器运算

典例:二分搜索(迭代器应用)

3.5数组

定义和初始化

指针和数组

C风格字符串

标准库与旧接口

多维数组

定义和初始化

下标引用

指针和多维数组&使用范围for语句处理多维数组

 区分传统for语句和范围for语句(重点!易混!)

类型命名简化多维数组的指针

 典例

小总结/补充




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个整数构成的数组

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值