using 有一个更细的用法就是直接指明命名空间中的名字
//using namespace::name; using std::cin;
一个要注意的点是:头文件中不应包含using声明
因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个using声明,那么每个使用了该头文件的文件都会有这个声明,这样可能会在不经意间产生命名冲突。
定义和初始化除了常用的以外,还可以通过指定数目生成连续的字符串。
string s(10, 'c');
string s1; string s2(s1); string s2 = s1; string s3("value"); string s3 = "value"; string s4(n, 'c'); string s5 = "hiya"; //使用等号一般都是拷贝 string s6("hello"); //直接 string s7(10, 'c'); //直接 string s8 = string(10, 'c'); //拷贝
操作 | 作用 |
---|---|
os<<s | 将s写到输出流os中,返回os, eg:cout<<s |
is>>s | 从is中读取字符串赋给s,字符串以空白分隔,返回is, eg:cin>>s |
getline(is, s) | 从is中读取字符串赋给s,字符串以空白分隔,返回is, eg:getline(cin, s) |
s.empty() | 判空 |
s.size() | 返回s中字符的个数 |
s[n] | 取第n个字符的引用,n从0算起 |
s1+s2 | 字符串拼接 |
s1=s2 | 用s2的副本拷贝给s1 |
s1!=s2 | 比较两个串,逐字符比较,对大小写敏感 |
<,<=,=>,> | 根据字典序比较,对大小写敏感 |
对于cin而言,读取字符串给string时,会忽略掉开头的空白,字符串的读入结束也是空白,当需要读入空白时,则需要使用getline, getline遇到换行结束
操作 | 作用 |
---|---|
isalnum(c) | 当c是字母或数组时为真 |
isalpha(c) | 当c是字母时为真 |
iscntrl(c) | 当c是控制字符时为真 |
isdigit(c) | 当c是数字时为真 |
isgraph(c) | 当c不是空格但可以打印时为真 |
islower(c) | 当c是小写时为真 |
isprint(c) | 当c是可打印字符时为真(即c为空格,或具有可视形式) |
ispunct(c) | 当c是标点符号时为真(即c不是控制字符、数字、字母、可打印空白中的一种) |
isspace(c) | 当c是空白时为真(即c是空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种) |
isupper(c) | 当c是大写字符时为真 |
isxdigit(c) | 当c是十六进制数字时为真,更直白的理解是不是(01,af,A~F)中的字符 |
tolower(c) | 如果c是大写字母,返回小写;否则原样 返回 |
toupper(c) | 如果c是小写字母,返回大写;否则原样 返回 |
配套类型size_type体现了标准库类型与机器无关的特性,在具体使用的时候,通过作用域操作符来表明名字size_type是在类string中定义的。即string::size_type。它肯定是无符号
的类型,所以尽量不要用int去定义s.size(),这样可能会带来问题
string s = "ddd" unsigned len = s.size();
string s = "dwdadwwadaw"; for(string::size_type i = 0; i < s.size(); i++) { s[i] = toupper(s[i]); } cout << s << endl;
原因:为了与C兼容,C++中string和字符串字面值是不同的类型!
用等号去赋值vector时,是拷贝,不是引用操作!
vector<int> vec = {0,1,2,3,4}; //列表初始化方法 vector<int> b = vec; //拷贝 vec[4] = 111; //b[4]并没有改变
使用vector指定元素数目初始化时要满足以下两个条件:
除非是所有元素值一样,那么定义一个空的vector然后逐个加入会比一开始指定vector的大小后添加可能更快
vector<int> v1(10); //10个元素,都是0 vetcor<int> v2{10}; //1个元素,10 vector<int> v3(10, 1); //10个元素,都是1 vectopr<int> v4{10, 1}; //2个元素,10、1 vector<string> v5{"hi"}; //列表初始化 1个元素 vector<string> v6("hi"); //错误! 不能用字符串字面值构建vector对象 vector<string> v7{10}; //10个默认初始化的元素 vector<string> v8{10, "hi"}; //10个值为"hi"的元素
由于编译器并不会检测出下标越界的情况,这可能会导致严重的缓冲区溢出(buffer overflow)错误。
为了便于专门的到const_iterator类型,C++11专门引入了两个函数cbegin()和cend()
const vector<int> cv; auto itr = cv.begin(); //vector<int>::const_iterator
解引用时必须加括号,因为不加括号相当于时访问it的成员,而it只是迭代器类型
(*it).empty(); //正确 *it.empty(); //错误
箭头运算符把解引用和成员访问两个操作结合在一起,也就是说it->mem和(*it).mem表达的意思相同
当使用迭代器时,如果容器如vector动态增长了(push_back),会使迭代器失效。
iter1 - iter2
两个迭代器的相减结果是他们之间的距离。
数组的声明中,维度必须是常量表达式。
unsigned cnt = 42; int a[cnt]; // 错误 constexpr unsigned sz = 42; int a[sz]; //正确
char a1[] = {'C', '+', '+'}; //列表初始化,没有空字符 char a2[] = {'C', '+', '+', '\0'}; //列表初始化,含有显式的空字符 char a3[] = "C++"; //自动添加表示字符串结束的空字符 const char a4[6] = "Daniel"; //错误,没有空间存放结束符
不能将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值。
int a[] = {0, 1, 2} int a2[] = a; //错误,不允许使用一个数组初始化另外一个数组 a2 = a; //错误,不能把一个数组直接赋值给另一个数组
就复杂数组而言,由内向外阅读,即先理解定义的名字是引用还是指针,然后在看外面是身累了类型的数组。
int *ptrs[10]; //ptrs是一个包含10个整型指针的数组 int &refs[10] = ?; //错误,不存在引用类型的数组 int (*Parray)[10] = &arr; //Parray是10个整型数组的指针 int (&arrRef)[10] = arr; //arrRef是10个整型数组的引用 int *(&arry)[10] = ptrs; //10个整型指针的数组的引用
数组的下标是size_t
类型,在头文件cstddef
头文件中
指针也是迭代器
C++11中有新特性 可以使用begin和end直接获取数组的头指针和尾后指针
int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int *beg = begin(ia); int *last = end(ia); ptrdiff_t len = last - beg;
两个指针相减的结果的类型是一种名为ptrdiff_t的标准库类型,和size_t一样,ptrdiff_t也是一种定义在cstddef头文件中的机器相关的类型。因为差值可能为负值,所以ptrdiff_t为有符号类型。
当两个指针指向同一个数组
的元素,或指向该数组的尾元素的下一位置,就能利用关系运算符对其进行比较。
int *b = arr, *e = arr + sz; while(b < e) { b++; }
下标和指针
int ia[] = {0, 2, 4, 5, 6, 7}; int *p = ia; int i = *(p + 2) //等价于 i = p[2] int *p = ia[2]; //这是错误的,出了ia是指针,其他的都要用& int k = p[-2]; //不会报错
标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求,即可为负值,但是并不像python中一样有实际意义
C风格的字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法。
char ca[] = {'C', '+', '+'}; cout << strlen(ca); //严重错误,ca没有以'\0'空字符结束
当使用C风格字符串时,非常容易出现安全问题,例如strcat函数,如果连接到前一个字符串大小不足以容纳拼接后到字符串,会导致严重的安全泄漏。
const char s1[] = "A string"; const char s2[] = "A different string"; int k = strcmp(s1, s2); //比较字符串函数, s1 = s2:k=0, s1 < s2:k<0, s1 > s2:k>0 char largeStr[100]; strcpy(largeStr, s1); //s1拷贝给largeStr strcat(largeStr, s2); //将s2连接到largeStr后
若要混用string和C风格字符串,需要使用c_str()函数。
需要注意的是,char指针可以初始化string,但是string不能初始化char指针
const char *str = s.c_str();
c_str函数返回的是const char*类型的,确保不会改变字符数组的内容,但我们无法保证c_str函数一直有效,如果后续操作改变了s的值就可能让之前返回的数组失去效用。如果需要一直使用或者想改变,最好拷贝一份。
不允许使用一个数组为另一个内置类型的数组赋初值,也不允许使用vector对象初始化数组。相反的,允许使用数组来初始化vector对象。只需要指出想要初始化数组的初始位置和尾后位置指针就可以了。
int in_arr[] = {0, 1, 2, 3, 4, 5}; vector<int> ivec(begin(in_arr), end(in_arr));
C++中没有多维数组,只有数组的数组
int ia[3][4] = {0}; //数组所有元素初始化为0 int ia[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} }; //上下两种初始化方式等价 int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11}; //显示地初始化每行的首元素, 其他元素执行默认值初始化 int ia[3][4] = {{0}, {4}, {8}}; //显示地初始化首行, 其他元素执行默认值初始化 int ia[3][4] = {0, 3, 6, 9}; //将row定义成一个含有4个整数的数组的引用,然后将其绑定到ia到第二行 int (&row)[4] = ia[1];
外层循环使用引用类型的原因是:
//true size_t cnt = 0; for(auto &row : ia) { for(auto &col : row) { col = cnt++; } } //true for(const auto &row : ia) { for(auto col : row) { cout << col << endl; } } //false for(auto row : ia) { for(auto col : row) { cout << col << endl; } }
上面代码最后一个错误的原因是:第一层循环其实是要取长度为n的数组。因为row不是引用类型,所以编译器初始化row时会自动将这些数组形式的元素转化成指向该数组内首元素的指针,这样row就是int*类型,那么第二层循环就不合法了,因为不能用auto去遍历int*类型。
要使用for去处理多维数组,除了最内层的循环外,其他所有循环的控制变量都要加引用类型
//指针声明中,圆括号必不可少 int ia[3][4]; int (*p)[4] = ia; //p指向含有4个整数的数组 p = ia[2]; //auto遍历 for(auto p = ia; p != ia + 3; ++p) { //这里的p其实是指向ia[0/1/2]数组的指针,*p才是数组 for(auto q = *p; q != *p + 4; ++q) { cout << *q << endl; } cout << endl; } //使用begin, end函数 for(auto p = begin(ia); p != end(ia); ++p) { //这里的p其实是指向ia[0/1/2]数组的指针,*p才是数组 for(auto q = begin(*p); q != end(*p); ++q) { cout << *q << endl; } cout << endl; }