3.1.命名空间using声明
using namespace::name;
这样就可以直接使用命名空间的名字了
using std::cout;
cout<<"hello world";
3.2.string
需要包含string头文件,在std命名空间中
#include <string>
using std::string;
定义和初始化string:
string s1; //默认,空字符串
string s2(s1); //直接初始化
string s2=s1; //拷贝初始化
string s3("hello"); //直接初始化
string s3="hello"; //拷贝初始化
string s4(5,'a'); //aaaaa
string对象上的操作:
string line;
getline(cin,line); //读取一行,换行符丢弃
auto length=line.size(); //返回无符号的整型
注意不要和有符号整型混用:line.size()<n
如果n是一个负数,判断结果几乎肯定为true,因为负数被自动转换为一个比较大的无符号值
历史原因,也为了与C兼容,C++中字符串字面值并不是标准库类型string的对象。
切记:字符串字面值与string是不同的类型
当把string对象和字符串字面值混在一条语句中使用时,必须确保每个加法运算符的两侧的运算对象至少有一个是string
string str="hellow"+" abc "; //错误
处理string对象中的字符:
遍历string:
string line = "abc";
for (auto c : line)
{
//c是引用,可以对c操作,如:改为大写字符
c = toupper(c);
cout << c << endl;
}
访问string对象中单个字符有两种方式:
1.使用下标[]
string line = "abc";
line[0] = 'x';
2.使用迭代器
3.3.vector:对象的集合,类型相同
vector<vector<int> > a; //早期版本> >中间有空格
vector<vector<int>> a; //C++11之后
定义和初始化:
vector<T> v1; //空vector,默认初始化
vector<T> v2(v1); //v2中包含v1的副本
vector<T> v2 = v1; //v2中包含v1的副本
vector<T> v3(n,val); //n个val
vector<T> v4(n); //n个初始化的对象
vector<T> v5 {a,b,c}; //列表初始化(C++11)
vector<T> v5={a,b,c};
操作:
vector<int> v1 = {1,2,3};
for (auto i : v1)
{
i += 1;
cout << i << endl;
}
迭代器:
这些类型都拥有名为begin和end的成员
begin成员负责返回指向第一个元素(或第一个字符)的迭代器。
end成员则负责指向容器(或string对象)“尾元素的下一位置”的迭代器,指示的是容器一个本不存在的尾后元素
特殊情况:容器为空,begin和end返回是同一个迭代器。
string s = "abc";
for (auto it = s.begin(); it != s.end(); ++it)
{
cout << *it << endl;
}
迭代器返回的类型:
iterator(可以修改对象)和const_iterator(不能修改对象)
cbegin()、cend() C++11新函数,返回const_iterator
箭头运算符:->
把解引用和成员访问两个操作结合在一起:(*it).mem 等价 it->mem
使用了迭代器的循环体,不要向迭代器所属的容器添加元素
二分查找字符
string s = "abc";
char sought = 'a';
auto beg = s.begin(), end = s.end();
auto mid = s.begin() + (end - beg) / 2;
while (mid != end && *mid != sought)
{
if (sought < *mid)
{
end = mid;
}
else
{
beg = mid + 1;
}
mid = beg + (end - beg) / 2;
}
3.5:数组
定义数组的时候必须指定数组的类型,不允许用auto关键字由初始值的列表推断类型。
字符数组:可以用字符串字面值对此数组初始化,用这种方式,要注意字符串字面值的结尾处还有一个空字符,这个空字符也会像字符串的其他字符一样被拷贝到字符数组中。
不能将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值。
理解复杂的数组声明:
规则:从内往外,先右后左
先从名字开始,先右后左。
int *(&arry)[10] = ptrs;
1.从内往外,先看名字:arry
2.再看右边,是括号,再看左边是&,所以arry是个引用
3.在看右边,是个数组,arry是数组的引用
4.在看左边,是int *,数组中的元素是int类型的指针
所以:arry就是一个含有10个int型指针的数组的引用。
指针和数组有非常紧密的联系,使用数组时编译器一般会把它转换成指针。
使用数组类型的对象其实是使用一个指向该数组首元素的指针。
string nums[] = {"a" , "b"};
string *p = &nums[0];
string *p2 = nums; //数组名表示指向数组首元素的指针
auto p3(nums); //p3是一个指针,指向nums的第一个元素,和下面等价
auto p3(&nums[0]); //上面的式子等价
p3 = 34; //错误:不能用int值给指针赋值
使用decltype关键字是上述转换不会发生,返回的是2个string构成的数组:
decltype(nums) p4 = {"a","b"};
p4 = p; //错误:不能用整型指针给数组赋值
p4[0] = "c"; //正确
指针也是迭代器,可以进行++,–,+=,-=操作
直接用指针容易出错,C++11引入了两个名为begin和end的函数(在iteratro头文件中),和容器的两个同名成员功能类似,不过数组不是类类型,这两个函数不是成员函数,整取使用形式是将数组作为它们的参数。
int arr[] = {0,1,2};
int *beg = begin(arr);
int *last = end(arr);
注意如果一个指针指向了某种内置类型数组的尾元素的“下一个位置”,尾后指针不能执行解引用和递增操作。
内置的下标运算符所用的索引值不是无符号类型,这一点与vector和string不一样。
标准库类型限定使用的下标必须是无符号类型
int arr = {1,2,3,4,5};
int i = arr[2]; //这一步是得到:arr+2所指的元素
int *j = &arr[2]; //arr[2]的值
int k = j[1]; //j[1]等价于*(j+1),就是arr[3]的值
int m = j[-2]; //等价*(j-2),就是arr[0]的值;
C风格字符串(不建议使用)
字符串字面值就是C风格字符串,C风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法。
按此习惯书写的字符串存放在字符数组中并以空字符结束,以空字符结束的意思是在字符串最后一个字符后面跟着一个空字符(‘\0’),一般用指针来操作这些字符串。
C风格字符串不能直接比较,因为本质是数组,不能用数组名进行比较,数组名代表的是指针,比较的结果是未定义,因为两个指针指向不是同一个对象。只要记得C风格字符串就是个数组,只能按照数组的操作来,就懂了。
比较需要用C风格的比较函数。
混用string对象和C风格字符串
很多C++程序在标准库出现之前就已经写成了,肯定没用到string和vector类型,而且有一些C++程序实际上是与C语言和其他语言的接口程序,当然无法使用C++标准库。
string s("hello");
如上:通过字面值字符串初始化string对象,字符串字面值的类型就是C风格的字符串
所以可以通过C风格字符串替代:任何出现字符串字面值的地方都可以用空字符结束的字符数组来替代
1.允许使用空字符结束的字符数组来初始化string对象或为string对象赋值
2.在string对象的加法运算中,允许使用以空字符结束的字符数组作为其中一个运算对象(不能两个运算对象都是);
在string对象的复合赋值运算中允许使用以空字符结束的字符数组所谓右侧的运算对象。
string s = "abc";
char* str = s; //错误:不能用string对象初始化char*
const char* str1 = s.c_str(); //正确:用c_str()函数
char* str1 = "abc"; //错误:因为字符串字面值虽然是C分隔字符串,但是它是不能修改的
const char* str1 = "abc"; //正确:必须加const
使用数组初始化vector对象:
前面已经说了:
不允许使用一个数组为另一个内置类型的数组赋初值,也不允许使用vector对象初始化数组。
相反,允许使用数组来初始化vector对象。
int arr[] = {0,1,2,3,4,5};
vector<int> vec(begin(arr),end(arr));
vector<int> vec2(arr+1,arr+4); //用三个元素来创建vec2:arr[1]、arr[2]、arr[3]
建议:现代C++程序应当尽量使用vector和迭代器,避免使用内置数组和指针;
尽量使用string,避免使用C风格的基于数组的字符串。
3.6:多维数组
严格来说C++没有多维数组,多维数组是数组的数组。
初始化:对二维数组来说,第一个维度称为行,第二个称为列
第一种:
int arr[3][4] = {
{0,1,2,3}, //第一行
{2,4,5,1},
{5,6,3,2}
}
第二种:
int arr[3][4] = {
{0}, //第一行
{2},
{5}
}
第三种:
int arr[3][4] = {
0,1,2,3,2,4,5,1,5,6,3,2
}
第四种:初始化第一行,其他执行默认值初始化
int arr[3][4] = {0,1,2,3}
遍历:
size_t count=0;
for(auto &row : arr)
for(auto &col : row)
{
col = count;
++count;
}
for(auto &row : arr)
for(auto col : row) //如果只读不写,可以去电引用
{
++count;
}
使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。
指针和多维数组
多维数组名转换得来的指针实际上是指向第一个内层数组的指针
int arr[3][4];
int (*p)[4] = arr; //p指向含有4个整数的数组
p = &arr[2];
int *a[4]; //整型指针的数组:数组里存放的是指针,a首先是一个数组
int (*a)[4]; //指向含有4个整数的数组:数组里存放的是整型,a是一个指针
遍历:使用auto自动推断
for(auto row = arr; row != arr + 3;++row)
{
//多维数组是数组的数组
//row指向最外层数组的第一个元素,即第一行
//*row指向第一行的数组首地址(*row还是地址)
for(auto col = *row;col != *row +4; ++q)
{
cout<<*col<<' ';
}
cout<<endl;
}
使用begin和end判断首尾
for(auto row = begin(arr); row != end(arr);++row)
{
for(auto col = begin(*row) ;col != end(*row); ++q)
{
cout<<*col<<' ';
}
cout<<endl;
}
类型别名简化多维数组的指针
using int_array = int[4];
typedef int int_array[4];
for(int_array *p = arr; p != arr+3; ++arr)
{
for(int *q = *p; q!=*p+4;++q)
{
cout<<*col<<' ';
}
cout<<endl;
}