《C++Primer》读书笔记(三)字符串、向量、数组

命名空间using声明

(1)本节学习最安全的方法使用using声明

using namespace::name
  • 注意:使用一个名字就要有其对应的uesing声明,如:using std::cin;

(2)头文件不应包含using声明
- 头文件将会被直接拷贝进入文件中,有using可能会引起不必要的冲突

标准库类型string

string类型表示可变长的字符序列

定义和初始化string对象

  • 常用初始化方式
string s1; //空字符串
string s2=s1; //s2是s1的副本
string s3="hiya"; //s3是该字符串的副本
string s4(10,'c'); //s4的内容是cccccccccc

(1)直接初始化和拷贝初始值
- 使用“=”是拷贝初始化,不使用等号则为直接初始化

string s5="hiya"; //拷贝初始化
string s6("hiya"); //直接初始化
  • 另外,没有必要将==直接初始化进行拷贝初始化==,如:string s8=string(10,’c’);

string对象上的操作

  • string的操作
操作结果
os << s将s写到输出流os当中,返回 os
is >> s从is中读取字符串赋值给 s,字符串以空白分隔,返回 is
getline(is,s)从is中读取一行赋值给s,返回 is
…………

(1)读写string对象

string s;
cin>>s;
cout<<s<<endl;
  • 在执行读取操作时,string对象会自动忽略开头的空白(空格、换行符、制表符等),直到遇见下一处空白为止

(2)读取未知数量的string

string word;
while (cin>>word) //反复读取,直到到达文件末尾
cout<<word<<endl; //逐个输出单词,每个单词后面紧跟一个换行符

(3)使用getline读取一整行
- 当我们希望最终得到的字符串中保留输入时的空白符时,可以使用getline来代替原来的>>

string line;
while (getline(cin,line))
  cout<<line<<endl;
  • getline直到读取到换行符为止,抱愧换行符也被读入,但是存入string对象时,又不存换行符。所以,得到的对象==不包含换行符==

(4)string 的empty和size操作
- 它们都是string的成员函数
- 调用方法:s.empty() 和 s.size()

(5)string::size_type 类型
- 对于size函数来说,返回的是int或者unsigned 似乎都是合情合理的,但其实它返回的是string::size_type类型的值
- 切记s.size()返回的是一个无符号整型,在表达式中混用带符号和无符号数将会引起麻烦

建议:如果一个表达式中,有了size就不要有int了,避免混用无符号数

(6)比较string对象

(7)为string对象赋值

(8)两个string对象相加
- 两个string相加将实现拼接的效果

(9)字面值和string对象相加
- 字符串字面值与string是不同的类型
- ==字面值不能与字面值==相加

处理string对象中的字符

  • 想要知道某个字符的特性,使用cctype头文件中定义的标准库函数
函数执行
isalnum(c)当c是字母或数字时为真
isalpha当c是字母时为真
…………

(1)处理每个字符?使用基于范围的for语句
- 范围for语句

for (declaration:expression)
  statement

其中,expression部分是一个对象,用于表示一个序列。declaration部分负责定义一个变量,该变量将被用于访问序列中的基础元素。每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值

string str("some string");
for(auto c:str)  //对于str中的每个字符
   cout<< c <<endl;  //输出当前字符,后面紧跟一个换行符

(2)使用范围for语句改变字符串中的字符

  • 要想改变string对象中字符的值,必须把循环变量定义成引用类型。因为引用只是给定对象一个别名,因此当使用引用作为循环控制变量时,这个变量实际上被依次绑定到了序列的每个元素上,使用这个引用,就能改变它绑定的字符
string s("Hello World");
for (auto &c:s) //对s中的每个字符
  c=toupper(c); //c是一个引用,因此赋值语句将改变s中字符的值
cout<<s<<endl

(3)只处理一部分字符?
- 要想访问string对象中的单个字符有两种方式:1.使用下标 2.迭代器
- 使用下标执行迭代

for(decltype(s.size() index=0;
   index !=s.size() && !isspace(s[index]);
   ++index)
    s[index]=toupper(s[index]);

注意检查下标的合法性

(4)使用下标执行随机访问

练习

1.编写一段程序,使用范围for语句将字符串中的所有字符换成X

string s="Hello World";
for(auto &c:s)
  c='X';
cout<<s<<endl;

2.下面的程序有何作用?合法?

string s;
cout<<s[0]<<endl;

显示s的第一个字符,但是因为s未初始化,所以s是一个空串

3.编程:读入带有标点符号的字符串,去除标点后输出

string s="H,e,l.l!o";
for(auto &c:s)
    if(ispunct(c))
        c=0;
cout<<s<<endl;

另一种可供参考的程序,实现真正的去除,以上程序是将标点符号换成了空串

标准库类型vector

定义和初始化vector对象

(1)初始化vector对象的方法

语句结果
vectorv1是一个空vector,它潜在的元素是T类型的,执行默认初始化
vector v2(v1)v2中包含有v1的所有元素的副本
vector v3(n,val)v3包含了n个重复的元素,每个元素的值都是val
vector v5{a,b,c…}v5包含了初始值个数的元素,每个元素被赋予相应值

- vector元素的拷贝

vector<int> ivec; //初始状态为空
// 在此处给ivec添加一些值
vector<int> ivec2(ivec); //把ivec的元素拷贝给ivec2
vector<int> ivec3=ivec; //把ivec的元素拷贝给ivec3
vector<string> svec(ivec2); //错误,svec的元素是string对象,不是int

(2)列表初始化vector对象(花括弧)

vector<string> v1{"a","an","the"};

(3)创建指定数量的元素

vector<int> ivec(10,-1); //10个int类型,每个都被初始化为-1

(4)值初始化

vector<int> ivec(10); //10个元素,没有都初始化为0
vector<int> vi=10; //错误,必须使用直接初始化的形式指定向量大小

向vector对象中添加元素

(1)push_back函数

vector<int> v2;
for(int i=0;i!=100;++i)
   v2.push_back(i);
vector<string> text;
string word;
while(cin>>word){
    text.push_back(word);
}

(2)用vector对象添加元素蕴含的编程假定
- 范围for语句体内不应改变其所遍历序列的大小
- 于是,在循环体内向vector添加元素的语句,则不能使用范围for循环

练习

1.编程:用cin读入一组整数,存入vector对象中

vector<int> num;
int new_num;
while(cin>>new_num){
    push_back(new_num);
}

其他vector操作

操作结果
v.empty()v不含有任何元素,返回真;否则为假
v.size()返回v中元素的个数
v.push_back(t)向v的末尾添加一个值为t的元素
v[n]返回v中第n个位置上元素的引用
v1=v2以v2的值拷贝替换v1

(1)访问vector对象中元素的方法
- 与string显示,可以用范围for来处理所有元素

vector<int> v{1,2,3,4};
for(auto &i:v){
    i*=i; //求元素值的平方
}
for(auto i:v){
    cout<<i<<endl;
}

(2)要使用size()返回的size_type

vector<int>::size_type //正确
vector::size_tyoe //错误

(3)计算vector内对象的索引
- 统计分数段的程序

vector<int> scores(11,0);
unsigned grade;
while(cin>>grade){
    if(grade<=100)
       ++scores[grade/10];
}

(4)不能用下标形式添加元素

vector对象(以及string对象)的下标运算符可用于访问已经存在的元素,而不能用于添加元素

==只能对确定已经存在的元素进行下标操作==

迭代器介绍

使用迭代器

  • 每一个有迭代器的类型都有begin和end的成员
  • begin负责返回第一个元素,end负责返回尾元素的下一位置
auto b=v.begin(),e=v.end();
  • 如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器

(1)迭代器运算符

运算符结果
*iter返回迭代器iter所指元素的引用
iter->mem解引用iter并获取该元素的名mem的成员,等价于(*iter).mem

- 小例子

string s("some string");
if(s.begin()!=s.end()){
    auto it=s.begin();
    *it=toupper(*it);
}

(2)将迭代器从一个元素移动到另一个元素
- 迭代器使用递增(++)运算符来从一个元素移动到下一个元素。从逻辑上说,迭代器的递增和整数的递增类似,整数的递增是在整数值上“+1”,而迭代器则是“向前移动一个位置”

因为end返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用的操作

for(auto it=s.begin();it!=s.end()&&!isspace(*it);++it)
   *it=toupper(*it);

(3)迭代器类型
- 拥有迭代器的标准库类型使用iterator和const_iterator

vector<int>::iterator it; //it能读写vector<int>的元素
string::iterator it2; //it2能读写string对象中的字符

vector<int>::const_iterator it3; //it3只能读元素,不能写元素
string::const_iterator it4; //it4只能读字符,不能写字符

(2)begin和end运算符
- 如果对象是常量,begin和end返回类型为:const_iterator;如果对象不是常量,返回iterator
- 为了可以直接得到const_iterator类型的返回值,C++中引入了cbegin和cend

(3)结合解引用和成员访问操作
- 解引用迭代器可获得迭代器所指的对象,如果该对象的类型恰好是类,就有可能希望进一步访问它的成员

vector<string> st;
auto it=st.begin();

(*it).empty();

*it.empty(); //错误,试图访问it的名为empty的成员,但it是迭代器,没有此成员

即如果it是vector对象的迭代器,使用(*it).empty()

  • 为了解决上述表达式,C++定义了==箭头运算符(->)==,it->mem和(*it).mem表达的意思相同
// 依次输出text的每一行直至遇到第一个空白行为止
for (auto it=text.cbegin();
    it!=text.cend()&&!it->empty();++it)
    cout<<*it<<endl;

(4)某些对vector对象的操作会使迭代器失效

  1. 不能在范围for循环中向vector对象添加元素
  2. 任何一种可能改变vector对象容量的操作都会使迭代器失效,比如push_back

迭代器运算

(1)迭代器的算术运算
- ==iter+n== 迭代器和一个整形数相加减,其返回值是移动若干个位置后的迭代器(结果指向一个元素或者指向尾元素的下一个位置)

auto mid-vi.begin()+vi.size()+2;
if(it<mid)
  // 处理vi前半部分的元素

(2)使用迭代器运算
- 使用迭代器运算的一个经典算法是二分搜索

auto beg=text.begin(),end=text.end();
auto mid=text.begin()+(end-beg)/2;

while(mid!=end && *mid!=sought){
    if(sought < *mid)
       end=mid;
    else
       beg=mid+1;
    mid=beg+(end-beg)/2;
}

数组

定义和初始化数组

  • 数组定义中,数组名字和数组的维度,都是数组的类型的一部分,编译的时候应该是已知的(即维度也必须是常量表达式)
  • 和内置类型一样,如果在函数内部定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值

(1)显式初始化数组元素
- 可以对数组进行列表初始化,此时允许忽略数组的维度
- 如果维度比提供的初始值数量大,则保存到前段,剩余部分默认初始化

const unsigned sz=3;
int ia1[sz]={0,1,2}; //含有3个元素,值分为为0,1,2
int a2[]={0,1,2}; //维度是3的数组
int a4[5]={0,1,2}; //等价于a3[]={0,1,2,0,0}
int a5[2]={0,1,2}; //错误,初始值过多

(2)字符数组的特殊性
- 字符数组有一种额外的初始化形式,可以用字符串字面值,但一定要注意字符串字面值最后还有一个空字符,同样也会被拷贝进入字符数组中

char a3[]="C++"; //结果会自动添加字符串结束的空字符

(3)不允许拷贝和赋值
- ==不能将数组的内容拷贝进其他数组作为其初始值,也不能用数组为其他数组赋值==

(4)理解复杂的数组声明

int *ptrs[10]; //ptrs是含有10个整型指针的数组
int &refs[10]=/*?*/; //错误,不存在引用的数组

int (*Parray)[10]=&arr; //Parray指向一个含有10个整型的数组
int (&arrRef)[10]=arr; //arrRef引用一个含有10个整数的数组
  • 更复杂的情况
int *(&arry)[10]=ptrs; // arry是数组的引用,该数组含有10各指针
  • ==要理解数组声明的含义,最好的办法是从数组的名字开始按照由内向外的顺序运输==

访问数组的元素

  • 在使用数组下表的时候,通常将其定义为size_t类型——一种机器相关的无符号类型
unsigned scores[11]={};
unsigned grade;
while(cin>>grade){
    if(grade<=100)
    ++scores[grade/10];
}
  • 与vector和strin一样,当要遍历所有元素时,用范围for很合适

(1)检查下标的值

大多数常见的安全问题都源于缓冲区溢出错误。当数组或其他类似数据结构的下标越界并试图访问非法内存区域时,就会产生此类错误

指针和数组

  • 数组的元素取地址符,就能得到指向该元素的指针
string nums[]={"one","two","three"};
string *p=&nums[0]; //p指向nums的第一个元素
  • 数组还有一个特性:在用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针

数组的操作实际上是指针的操作

  • 所以,==当使用数组作为一个auto变量的初始值时,其类型是指针而非数组==

  • 但是,使用decltype关键字时,不会发生上述转换,decltype(ia)返回的类型是对应的数组类型

(1)指针也是迭代器
- 指向数组元素的指针拥有其他迭代器的运算

int arr[]={0,1,2,3};
int *p=arr;
++p;
  • 需要注意的是,==尾后指针不能执行解引用或者递增操作==

(2)标准库函数begin和end
- 数组的尾后指针及其容易出错,所以C++11引入了begin和end函数,但是,数组不是类类型,所以这两个函数不是成员函数,正确方法:

int ia[]={0,1,2,3,4,5,6,7,8,9};
int *beg=begin(ia);
int *last=end(ia);
  • 例子:假设arr是一个整型数组,下面的程序负责找到arr中的第一个负数
int *pbeg=begin(arr),*pend=end(arr);
while(pbeg!=pend&&*pbeg>=0)
     ++pbeg;

(3)指针运算
- 指向数组的指针可以自信所有迭代器的运算,包括解引用、递增、比较、与整数相加、两个指针相减等
- 两个指针相减的结果等于它们之间的距离(必须指向同一个数组当中的元素),两个指针相减的结果类型为:ptrdiff_t类型,是与机器相关的带符号类型
- 指向同一个数组的元素的两个指针,可以比较大小,实际为比较它们地址的先后

// 实现遍历
int *b=arr,*e=arr+sz;
while(b<e){
    ++b;
}

(4)解引用和指针运算的交互
- 指针加上一个整型后依然是指针,所以同样可以解引用(即获得数组元素后面n位的元素)

(5)下标和指针

int *p=&ia[2]; // p指向索引为2的元素
int j=p[1]; // j指向索引为3的元素
int k=p[-2]; // p[-2]是ia[0]表示的元素

C风格字符串

(1)比较字符串
- 在C++标准库string对象的方法,比较字符串的时候,用的是普通关系运算符,但是,如果用在C风格字符串上,实际将比较的是指针而非字符串本身:
- C风格字符串比较的方法

if(strcmp(ca1,ca2)<0) // 和两个string对象的比较s1<s2效果一样

(2)目标字符串的大小由调用者指定
- 使用C风格字符串连接string对象要使用strcat函数和strcpy函数,而且还需要准备存放结果的字符串

strcpy(largeStr,ca1);
strcat(largeStr," ");
strcar(largeStr,ca2);

但是,如此及其易发生错误!!!==最好使用标准库string==

与旧代码的接口

(1)混用string对象和C风格字符串
- 任何出现字符串字面值的地方都可以用以空字符结束的字符数组来替代
1. 允许使用空字符串结束的字符数组来初始化string对象或为string对象赋值
2. 在string对象的加分运算中允许使用以空字符结束的字符数组作为其中要给运算对象(不能两个运算对象都是);在string对象的复合赋值运算中允许使用以空字符串结束的字符数组作为右侧的运算对象
- 上述性质反过来则不成立:如果程序某处需要一个C风格字符串,无法直接用string对象来替代它。例如,不能用string对象直接初始化指向字符的指针。为了完成该功能,sting提供了c_str函数

char *str=s; // 错误:不能用string对象初始化char*
const char *str=s.c_str(); //正确

如果执行完C_str()函数后程序想一直都使用其返回的字符数组,最好将该数组重新拷贝一份

(2)使用数组初始化vector对象
- 数组不能用数组赋初值,也不允许用vector对象初始化。
- 但是,允许用数组来初始化vector对象(只需指明要拷贝的首元素地址和尾后元素地址)

int int_arr[]={0,1,2,3,4,5};
vector<int> ivec(begin(int_arr),end(int_arr))

多维数组

C++语言中实际上没有多维数组,通常说的多维数组其实是数组的数组

int ia[3][4]; //大小为3的数组,每个元素是含有4个整数的数组

int arr[10][20][30]; //大小为10的数组,每个元素都是大小为20的数组,20个数组元素是含有30个整型的数组

(1)多维数组的初始化

int ia[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};

int ia{3}[4]={1,2,3,4,5,6,7,8,9,10,11};

int int[3][4]={{0},{4,},{8}}; //只声明每行的首个元素

(3)多维数组的下标引用
- 使用下标运算符来访问多维数组的元素

ia[2][3]=arr[0][0][0]; //用arr的首元素为ia第3的第4个元素赋值
int (&row)[4]=ia[1]; ..把row绑定到ia的第二个4元素数组上

(4)使用范围for语句处理多维数组

size_t cnt=0;
for(auto &row:ia) //对于外层数组的每一个元素
    for(auto &col:row){ //对于内层数组的每一个元素
        col=cnt;
        ++cnt;
    }

要使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型

(5)指针和多维数组

int ia[3][4]; //大小为3的数组,每个元素是含有4各整数的数组
int (*o)[4]=ia; //p指向含有4个整数的数组
p=&ia[2]; //p指向ia的尾元素
for(auto p=begin(ia);p!=end(ia);++p){ //p指向ia的第一个数组
    for(auto q=begin(*p);q!=end(*p);++q) //q指向内层数组的首元素
        cout<<*q<<" "<<end;
}

(6)类型别名简化多维数组的指针
- 读、写和理解一个指向多维数组的指针是一个让人不厌其烦的工作,使用类型别名能让这个各做变得跟简单

using int_array=int[4];
typedef int int_array[4];

for(int_array *p=ia;p!=ia+3;++p){
    for(int *q=*p;q!=*p+4;++q)
        cout<<*q<<""<<endl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值