C++primer-learning-notes-chapter3

318 阅读6分钟

using声明


  • 头文件不应包含using声明

标准库类型string


#include <string>
using std::string

定义和初始化string对象

string s1
string s2(s1)
string s2 = s1
string s3("value")
string s3 = "value"
string s4(n,'c')

直接初始化和拷贝初始化

string s5 = "yiya";  //字面值和string是不同的类型
string s6("yiya");
string s7(10,'c');
string s8 = string(10,'c'); //多个值初始化时,强行用拷贝初始化

string对象上的操作

os << s	//将s写到输出流os当中,返回os
is >> s	//从is中读取字符串赋给s,字符串以空白分割,返回is
getline(is, s)	//从is中读取一行赋给s,返回is
s.empty()	//s为空返回true,否则返回false
s.size()	//返回s中字符的个数
s[n]	//返回s中第n个字符的引用,位置n从0计起
s1+s2	//返回s1和s2连接后的结果
s1=s2	//用s2的副本代替s1中原来的字符
s1==s2	//如果s1和s2中所含的字符完全一样,则它们相等;string对象的相等性判断对字母的大小写敏感
s1!=s2	//同上
<, <=, >, >=	//利用字符在字典中的顺序进行比较,且对字母的大小写敏感
int main(){
    string s;
    cin >> s;  //将string对象读入s,遇到空白停止
    cout << s << endl;
    return 0;
}
#include <string>
#include <iostream>
using namespace std;

int main(){
    string word;
    while(cin>>word){
        cout<<word<<endl;
    }
    return 0;
}
int main(){
    string line;
    while(getline(cin,line)){ //读一整行,包括空白符。读入换行符但不存入string对象中。
        cout<<line<<endl;
    }
    return 0;
}

string::size_type类型

  • string.size() 的返回类型 是无符号类型的值
  • 可通过auto,decltype推断变量的类型:auto len = line.size()那么len的类型就是string::size_type
  • 表达式中有了size()函数就不要在使用int,可避免混用int和unsigned可能带来的问题。

字面值与string相加

  • 加法运算符的两侧的运算对象至少有一个是string
string s5 = s1 + "," + "hello";//合法
string s6 = "hello" + "," + s2;//非法

处理string对象中的字符

  • ctype.h VS cctype :C++修改了C标准库,名称去掉.h,前面加c
  • cctype头文件定义了一组标准函数
isalnum(c)	//当c是字母或数字时为真
isalpha(c)	//当c是字母时为真
iscntrl(c)	//当c是控制字符时为真
isdigit(c)	//当c是数字时为真
isgraph(c)	//当c不是空格但可以打印时为真
islower(c)	//当c是小写字母时为真
isprint(c)	//当c是可打印字符时为真
ispunct(c)	//当c是标点符号时为真
isspace(c)	//当c是空白时为真(空格、横向制表符、纵向制表符、回车符、换行符、进纸符)
isupper(c)	//当c是大写字母时为真
isxdigit(c)	//当c是十六进制数字时为真
tolower(c)	//当c是大写字母,输出对应的小写字母;否则原样输出c
toupper(c)	//当c是小写字母,输出对应的大写字母;否则原样输出c
  • 遍历字符串 for语句
string str("some string");
for(auto c : str){
    cout << c << endl;
}
string s("hello world!!!");
decltype(s.size()) punct_cnt = 0;
for(auto c:s){
    if(ispunct(c)):
        punct_cnt += 1;
}
cout<<punct_cnt<<"punctuation characters in"<<s<<endl;
  • 改变字符串中的字符 for语句
string s("hello");
for(auto &c : s):
    c = toupper(c);
cout<<s<<endl;
  • 下标运算符 可直接赋新值
string s("some string");
if(!s.empty())
    s[0] = toupper(s[0])
for(decltype(s.size()) index = 0;index != s.size() && !isspace(s[index]);++index)
    s[index] = toupper(s[index])
const string hexdigits = "0123456789ABCDEF";
string result;
string::size_type n;
while(cin>>n)
    if(n<hexdigits.size())
        result += hexdigits[n];
    cout<<result<<endl;

标准库类型vector


  • vector是一个容器,也是一个类模板
#include <vector>
using std::vector;
  • 容器包含其他对象。类模板本事不是类,但可以实例化出一个类。vector是一个模板,vector<>是一个类型。
vector<int> ivec;
vector<Sales_item> Sales_vec;
vector<string> file;
vector<vector<string>> file;
  • 初始化
vector<T> v1	//v1是一个空vector,它潜在的元素是T类型的,执行默认初始化
vector<T> v2(v1)	//v2中包含有v1所有元素的副本
vector<T> v2 = v1	//等价于v2(v1),v2中包含v1所有元素的副本
vector<T> v3(n, val)	//v3包含了n个重复的元素,每个元素的值都是val
vector<T> v4(n)	//v4包含了n个重复地执行了值初始化的对象
vector<T> v5{a, b, c...}	//v5包含了初始值个数的元素,每个元素被赋予相应的初始值
vector<T> v5={a, b, c...}	//等价于v5{a, b, c...}
  • 列表初始化
vector<string> articles = {"a","an","the"};
vector<string> articles{"a","an","the"};
  • 值初始化 int型默认初始值自动设为0,string,默认初始值设为空string对象
  • 注意 () {} 的区别
vector<int> v1(10);
vector<int> v2{10};
vector<string> v3{"hi"}; //列表初始化
vector<string> v4("hi");//错误
vector<string> v5{10};//并不是列表初始化,int型不能作为初值,编译器将其处理为 有10个默认值初始化的元素
vector<string> v6{10"hi"};//同上

向vector对象中添加元素

vector<int> v2;
for(int i=0;i!=100;i++)
    v2.push_back(i);
    
string word;
vector<string> text;
while(cin>>word){
    text.push_back(word);
}
  • 若循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环,原因见5.4.3

其他vector操作

v.emtpy()	//如果v不含有任何元素,返回真;否则返回假
v.size()	//返回v中元素的个数
v.push_back(t)	//向v的尾端添加一个值为t的元素
v[n]	//返回v中第n个位置上元素的引用
v1 = v2	//用v2中的元素拷贝替换v1中的元素
v1 = {a,b,c...}	//用列表中元素的拷贝替换v1中的元素
v1 == v2	//v1和v2相等当且仅当它们的元素数量相同且对应位置的元素值都相同
v1 != v2	//同上
<,<=,>, >=	//以字典顺序进行比较
vector<int> v{ 1,2,3,4,5,6,7,8,9 };
	for (auto& c : v)
		c *= c;
	for (auto c : v)
		cout << c << endl;
vector<int>::size_type;//正确
vector::size_type;//错误

计算vector内对象的索引

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

不能用下标形式添加元素 因为刚开始vector是不包含任何元素的,也就无下标一说。正确方式为push_back

vector<int> ivec;
for(decltype(ivec.size()) index = 0;index!=10;++index)
    ivex.push_back(index);

迭代器iterator


  • 所有标准库容器都可以使用迭代器。
  • 类似于指针类型,迭代器也提供了对对象的间接访问。

使用迭代器

auto b = v.begin(),e = v.end();//e表示v尾元素的下一位置
  • 如果容器为空, begin()和 end()返回的是同一个迭代器,都是尾后迭代器。
  • 使用解引用符*访问迭代器指向的元素。
  • 养成使用迭代器和!=的习惯(泛型编程)。
  • 容器:可以包含其他对象;但所有的对象必须类型相同。
  • 迭代器(iterator):每种标准容器都有自己的迭代器。C++倾向于用迭代器而不是下标遍历元素。
  • const_iterator:只能读取容器内元素不能改变。
  • 箭头运算符: 解引用 + 成员访问,it->mem等价于 (*it).mem‘
  • 谨记:但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
*iter	//返回迭代器iter所指向的元素的引用
iter->mem	//等价于(*iter).mem
++iter	//令iter指示容器中的下一个元素
--iter	//令iter指示容器中的上一个元素
iter1 == iter2	//判断两个迭代器是否相等
string s("some string");
if(s.begin() != s.end())
    auto it = s.begin();
    *it = toupper(*it);
for(auto it = s.begin();it != s.end() && !isspace(*it);++it)
    *it = toupper(*it);

迭代器类型:iterator,const_iterator

vector<int>::iterator it;
string::iterator it2;
vector<int>::const_iterator it3; //只读元素
string::const_iterator it4;

begin和end运算符

vector<int> v;
const vector<int> cv;
auto it1 = v.begin();//it1类型是vector<int>::iterator
auto it2 = cv.begin();//it2类型是vector<int>::const_iterator
  • 为便于专门得到const_iterator类型的返回值,引入cbegin,cend
auto it3 = v.cbegin(); //it3 为vector<int>::const_iterator
//不论v本身是否是常量,都返回const_iterator

结合解引用和成员访问操作

(*it).empty();
it->item;//等价于(*it).item
for(auto it=text.cbegin();it!=text.cend() && !it->empty();++it)
    cout<<*it<<endl;
  • 迭代器失效的情况:不能在范围for循环中向vector对象添加元素;push_back会使该vector对象的迭代器失效。见9.3.6

迭代器运算

iter + n	//迭代器加上一个整数值仍得到一个迭代器,迭代器指示的新位置和原来相比向前移动了若干个元素。结果迭代器或者指示容器内的一个元素,或者指示容器尾元素的下一位置。
iter - n	//迭代器减去一个证书仍得到一个迭代器,迭代器指示的新位置比原来向后移动了若干个元素。结果迭代器或者指向容器内的一个元素,或者指示容器尾元素的下一位置。
iter1 += n	//迭代器加法的复合赋值语句,将iter1加n的结果赋给iter1
iter1 -= n	//迭代器减法的复合赋值语句,将iter2减n的加过赋给iter1
iter1 - iter2	//两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动差值个元素后得到左侧的迭代器。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一位置。
>、>=、<、<=	//迭代器的关系运算符,如果某迭代器
auto mid = vi.begin() + vi.size()/2;
if(it<mid)
    //处理vi前半部分的元素
  • difference_type类型是带符号整型数,表迭代器间距离

使用迭代器进行二分搜索

//text为有序的
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;
}

数组(大小固定)


  • 初始化:char input_buffer[buffer_size];,长度必须是const表达式,或者不写,让编译器自己推断。
  • 显式初始化,初始化字符数组时注意空字符。
  • 数组不允许直接赋值给另一个数组。也不允许使用一个数组初始化另一个数组。
//不存在引用的数组,引用不是对象
int (*Parray)[10] = &arr;
int (&arrRef)[10] = arr;//数组的引用
int *(&array)[10] = ptrs;//arry是数组的引用,该数组包含10个指针

访问数组元素

  • 数组下标的类型:size_t 。
  • 字符数组的特殊性:结尾处有一个空字符,如 char a[] = "hello";
  • 用数组初始化 vector: int a[] = {1,2,3,4,5}; vector v(begin(a), end(a)); 。

数组和指针

int ia[] = {0,1,2,3,4,5,6,7,8,9};
auto ia2(ia);// auto ia2(&ia[0]) //ia2是一个整型指针

int *e = &arr[10];
for(int *b=arr;b!=e;++b)
    cout<<*b<<endl;
  • 标准库函数begin和end
int ia[] = {0,1,2,3,4,5,6,7,8,9}; 
int *beg = begin(ia);
int *last = end(ia);
  • 指针相减结果类型ptrdiff_t
  • 下标和指针
int ia[] = {0,2,4,6,8};
int *p = &ia[2];
int j = p[1]; //p[1]等价于*(p+1)
int k = p[-2];//p[-2]是a[0]表示的那个元素   //内置的下标运算符索引值不是无符号类型

C风格字符串

  • 用于c风格字符串的函数,在头文件cstring中。即C中string.h
strlen(p)	//返回p的长度,空字符不计算在内
strcmp(p1, p2)	//比较p1和p2的相等性。如果p1==p2,返回0;如果p1>p2,返回一个正值;如果p1<p2,返回一个负值。
strcat(p1, p2)	//将p2附加到p1之后,返回p1
strcpy(p1, p2)	//将p2拷贝给p1,返回p1
char ca[] = {'C','b','c'};
cout<<strlen(ca)<<endl; //错误:ca没有一空字符结束。strlen直到遇到空字符才停下来
  • 比较字符串 不能跟string对象比,需要用到strcmp函数
const char ca1[] = "";
const char ca2[] = "";
if(ca1<ca2) //未定义的:试图比较两个无关地址
if(strcmp(ca1,ca2)<0)
  • 连接字符串 有风险
strcpy(largeStr,ca1);
strcat(largeStr," ");
strcat(largeStr,ca2);

混用string对象和c风格字符串

string s("hello");
char *str = s;//错误:不能用string对象初始化char*
const char *str = s.c_str();//c_str函数返回值是一个c风格的字符串,是指针。
//为保证c_str返回的数组一直有效,可重新拷贝一份。

使用数组初始化vector对象

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

多维数组


  • 即数组的数组,一个维度表示数组本身大小,另外一个维度表示其元素大小。
int (&row)[4] = ia[1];//把row绑定到ia的第二个4元素数组上

constexpr size_t rowCnt=3,colCnt=4;
int ia[rowCnt][colCnt];
for(size_t i=0;i!=rowCnt;++i){
    for(size_t j=0;j!=colCnt;++j){
        ia[i][j] = i*colCnt+j;
    }
}

//for语句
size_t cnt = 0;
for(auto &row : ia) //使用引用 避免数组被自动转成指针
    for(auto &col : row){//使用引用  为了改变值
        col = cnt;
        ++cnt;
    }
    
for(const auto &row : ia)
    for(auto col : row)
        cout<<col<<endl;
  • 使用范围for语句时,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。

指针与多维数组

int ia[3][4];
int (*p)[4] = ia;
p = &ia[2];

for(auto p = ia;p!=ia+3;++p)
    for(auto q = *p;q!=*p+4;++q)
        cout<<*q<<' ';
    cout<<endl;
    
//使用标准库函数begin和end
for(auto p =begin(ia);p!=end(ia);++p){
    for(auto q=begin(*p);q!=end(*p);++q)
        cout<<*q<<' ';
    cout<<endl;
}

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

using int_array = int[4]; // c++11 下类型别名的声明
typedef int int_array[4]; // 等价的typedef声明
for(int_array *p = ia;p!=ia+3;++p){
    for(int *q = *p; q!=*p+4;++q)
        cout<<*q<<' ';
    cout<<endl;
}