C++初始化变量有好几种方法: int a=0; int a={0}; int a{0}; 都可以。 如果要在多个文件中使用同一个变量,就必须将声明和定义分离。此时,变量的定义必须出现在且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义。 如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显式地初始化变量(任何包含了显式初始化的声明即成为定义。): extern int i; // 声明i而非定义i int j; // 声明并定义j (1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化,最好也初始化,否则容易出错)。 (2)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。 它的作用是为变量起一个别名。假如有一个变量a,想给它起一个别名b,可以这样写: int a; int &b=a; int *p= &a; 这里&是取地址符。表示指针p指向(本语句等价于
int *p; p= &a;)
这就表明了b是a的“引用”,即a的别名。经过这样的声明,使用a或b的作用相同,都代表同一变量。 int &b=a2;//企图使b变成a2的别名(引用)是不行的。这样是错误的。 引用不是一个对象,所以不能定义引用的引用。 const char *p = "hello world";
指针指向的变量不可以改变,指针本身可以改变(即指向常量的指针)
这种情况下,以下语句是合法的:
char c = *p;
p++;
以下语句不合法:
*p = 'a';
p[5] = 'b'; char *const p表示指针指向的变量可以改变,但指针本身不能改变。
比如
int i=4;
int *const p = &i;
那么*p = 5;是合法的,p++是不合法的 const char *const p
两者都不可以改变。 理解这些声明的技巧在于,查看关键字const右边来确定什么被声明为常量 ,如果该const的右边是类型,则值是常量(底层const);如果const的右边是指针变量,则指针本身是常量(顶层const)。赋值拷贝的时候要特别注意底层const,一般来说,非常量可以赋值给常量,反之则不行。 例如const int *a = int *b; 反之不行。 C++ 11中引入的auto主要有两种用途:自动类型推断和返回值占位。auto在C++ 98中的标识临时变量的语义,由于使用极少且多余,在C++ 11中已被删除。 auto自动类型推断,用于从初始化表达式中推断出变量的数据类型。通过auto的自动类型推断,可以大大简化我们的编程工作。下面是一些使用auto的例子。 auto a; // 错误,没有初始化表达式,无法推断出a的类型 auto int a = 10 // 错误,auto临时变量的语义在C++ 11中已不存在 auto a = 10 auto c = 'A' auto s("hello"); vector<</span>int> vctTemp; auto it = vctTemp.begin(); auto ptr = [](){ cout << "hello world" << endl; }; 另外,在使用模板技术时,如果某个变量的类型依赖于模板参数,不使用auto将很难确定变量的类型(使用auto后,将由编译器自动进行确定)。下面是一个具体的例子。 template <</span>class T, class U> void Multiply(T t, U u) { auto v = t*u; } auto返回值占位,主要与decltype配合使用,用于返回值类型后置时的占位。 template <</span>class T, class U> auto Multiply(T t, U u)->decltype(t*u) { typedef decltype(t*u) NewType; NewType *pResult = new NewType(t*u); return *pResult; } 至于为什么需要将返回值类型后置,这里简单说明一下。如果没有后置,则函数声明为decltype(t*u) Multiply(T t, U u),但此时模板参数t和u还未声明,编译无法通过。 如果这个表达式是个函数,decltype
给出的类型为函数返回值的类型。 [cpp] view plaincopy int add(int i, int j){ return i+j; } decltype(add(5,6)) var = 5;//Here the type of var is return of add() -> which is int 非常重要的标记一下,decltype
不会执行表达式而auto会,他仅仅推论一下表达式的类型。
int foo(){} decltype( foo() ) x; // x is an int and note that // foo() is not actually called at runtime
头文件不应包含using 声明
string s1="hello", s2="world"; string s3=s1+","+s2; string s4 =s1+","; string s4 =s1+s2; 而+两侧运算对象至少一个是string类型: string s6="hello"+","+s2;
//错误, 不能把字面值直接相加
基于范围的for 语句 遍历给定序列中每个元素并操作 简单例子:把string对象中的字符每行一个个的输出出来
string str("some strings"); for (auto c : str) cout<<c<<endl; 这里使用auto 让编译器来决定c变量的类型,这里是char型
但如果想改变string 对象中的字符值,循环变量必须定义成引用 比如将string对象字符全变为大写 string str("some strings"); for (auto &c : str) c=toupper(c); cout<<s<<endl; 这里toupper函数是定义在〈cctype〉头文件中的处理string中的某个特定字符的集合。
标准模板库类型vector 表对象的集合。一般用{ }初始化,不容易错。 vector 〈string〉 v1{"a","ab","abc"}; 除了以下情况用()初始化 vector 〈string〉 svec(10, "hi!"); 表示10个string 类型的元素,每个都被初始化为"hi!"。 vector 〈int〉svec(10); 表示10个int 类型的元素,每个都被初始化为0。 vector 〈int〉svec(10,1); 表示10个int 类型的元素,每个都被初始化为1。 如果这时使用{} vector svec{10}; 表示1个int类型元素,该元素是10。 vector 〈int〉svec{10,1}; 表示2个int类型元素,分别是10和1。 它的成员函数push_back() vector 〈int〉 v2; for(int i=0; i!=100; ++i) v2.push_back(i); //依次将整数值放到v2的尾端 。。使用C++,在for循环中要习惯使用!=而不是﹤
迭代器 (1) 每种容器类型都定义了自己的迭代器类型,如vector:
vector::iterator iter;这条语句定义了一个名为iter的变量,它的数据类型是由vector定义的iterator类型。
(2) 使用迭代器读取vector中的每一个元素:
vector ivec(10,1);
for(vector::iterator iter=ivec.begin();iter!=ivec.end();++iter)
{
*iter=2; //使用 * 访问迭代器所指向的元素
}
其中begin()成员函数指向容器中的第一个元素,而end()成员函数其实指向最后一个元素的下一个元素,所以没什么真正含义,只是一个标记而已,如果v.begin()=v.end() ,则容器为空。
const_iterator:
只能读取容器中的元素,而不能修改。
for(auto it=ivec.cbegin();it!=ivec.cend();it++)
{
cout<<*it;
}
这里cbegin()和cend()是常量迭代器,是const_interator的类型,只能用于读取容器中的元素,不能修改容器中的元素。
注意,但凡是使用了迭代器的循环体,都不要想迭代器所属的容器中添加元素。
解引用操作符:所有迭代器都提供了解引用操作符(*),用于获取迭代器所指向的元素。以下代码都是合法的。
std::cout
<<
*iter;
*iter
=
5;
*iter
=
*iter
+
5; |
取后继元素操作符:所有迭代器都可以通过 iter++、++iter 操作符获取其后继元素的迭代器。
auto pbeg=v.begin();
while(pbeg!=v.end() && *pbeg>=0) //输出元素直到第一个负值
cout<<*pbeg++<<endl;
等价于*(pbeg++) , 解引用 返回的是pbeg未改变之前的值,然后再将指针pbeg+1。 这是一种广泛的用法。
不过注意,C++中一般更倾向于使用前置++, 因为这样先改变指针再返回指针改变后所指向的值,节省空间并且更易理解。
迭代器运算
it+n 表示迭代器所指示的位置相比原来的向右(前)移动了n个元素,
it-n 表示迭代器所指示的位置相比原来的向左(后)移动了n个元素,
数组的维度必须是一个常量表达式:
int a[10];
int a[var]; //除非var是constexpr,否则错误。
在C++中,数组大小固定,,对某些程序性能较好,但灵活性较差,如果不清楚元素的确切个护士,请使用vector.
不能把一个数组直接赋值给另一个数组。也不允许用vector对象来初始化数组。但允许使用数组来初始化vector 对象。
不存在引用的数组。
数组和指针关系密切:
string nums[] ={"one","two","three"};
//这里没声明数组的大小,但编译器会根据初始值的数量推算出为3
string *p=&nums[0];
多维数组的初始化
允许使用花括号括起来的一组值初始化多维数组,这点和普通的数组一样。下面的初始化形式中,多维数组的每一行分别用花括号括了起来:
int ia[3][4] = {
// 三个元素,每个元素都是大小为4的数组
{0, 1, 2, 3},
// 第1行的初始值
{4, 5, 6, 7},
// 第2行的初始值
{8, 9, 10, 11}
// 第3行的初始值
};
其中内层嵌套着的花括号并非必需的,例如下面的初始化语句,形式上更为简洁,完成的功能和上面这段代码完全一样:
// 没有标识每行的花括号,与之前的初始化语句是等价的
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
程序中经常会用到两层嵌套的for循环来处理多维数组的元素:
constexpr size_t rowCnt = 3, colCnt = 4;
int ia[rowCnt][colCnt]; // 12 个未初始化的元素
// 对于每一行
for (size_t i = 0; i != rowCnt; ++i) {
//对于行内的每一列
for (size_t j = 0; j != colCnt; ++j) {
// 将元素的位置索引作为它的值
ia[j] = i * colCnt + j;
}
}
外层的for循环遍历ia的所有元素,注意这里的元素是一维数组;内层的for循环则遍历那些一维数组的整数元素。此例中,我们将元素的值设为该元素在整个数组中的序号。
使用范围 for语句处理多维数组
由于在C++11新标准中新增了范围for语句,所以前一个程序可以简化为如下形式:
size_t cnt = 0;
for (auto &row : ia)
// 对于外层数组的每一个元素
for (auto &col : row) {
// 对于内层数组的每一个元素
col = cnt;
// 将下一个值赋给该元素
++cnt;
// 将 cnt加1
}
因为要改变元素的值,所以得把控制变量row和col声明成引用类型。
标准库函数 begin和end 跟容器的bengin和end 成员函数功能差不多。不过毕竟数组不是类类型,所以不太一样。
int ia[] = {0,1,2,3,4,5,6,7};
int *beg = begin(ia); //指向ia的首元素
int *last = end(ia); // 指向数组ia的最后一个元素的下一个位置。
位与,位或,位异或
7&8 = 0000 0111 & 0000 1000 = 0000 0000 = 0
7|8 = 0000 0111 | 0000 1000 = 0000 1111 = 15
7^8 = 0000 0111 ^ 0000 1000 = 0000 0111 = 7 |