变量声明和定义
c++支持分离式编译(separate compilation),程序分割成若干个文件,每个文件可独立编译。
声明(declaration):使得名字为程序所知,一个文件使用别处定义的名字必须包含对这个名字的声明。
定义(defination):创建与名字关联的实体。
例子:
extern int i; //声明i而非定义i
int j; //声明且定义j
extern double pi = 3.14; //定义
Note:变量只能被定义一次,可以多次声明。
引用
引用即本身不是一个对象,不能定义引用的引用。
引用只能绑定在对象上。
引用必须被初始化。
指向指针的引用:
int i = 42;
int *p;
int *&r = p; //r是一个对指针p的引用
const限定符
定义这样一种变量,它的值不能被改变:
const int bufSize = 512;
const对象必须初始化:
const int k; //错误
默认情况,const对象被设定为仅在文件内有效。
const的引用
对常量的引用(reference to const):把引用绑定到const对象上。
const int ci = 1024;
const int &r1 = ci;
允许一个常量引用绑定非常量对象:
int i =42;
const int &rl = i;
指针和const
指向常量的指针(pointer to const):存放常量对象的地址。
const double pi = 3.14;
const double *cptr = pi;
允许指向常量的指针指向非常量对象:
double dval = 3.14;
cptr = &dval;
const指针:指针是对象而引用不是,可以定义指针本身为常量。常量指针(const pointer)必须初始化、
int errNumb = 0;
int *const curErr = &errNumb; //curErr将一直指向errNumb
指针本身是一个常量并不意味着不能通过指针修改其所指的值,取决于所指对象的类型。
*curErr = 0; //正确,把curErr所指对象的值重置
常量表达式:值不会改变且在编译过程就能得到计算结果的表达式。
声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化:
constexpr int mf = 20;
best practices:认为变量是一个常量表达式,就声明成constexpr类型。
auto类型说明符
编程时常常要把表达式的值赋给变量,要求在声明变量的时候清楚地知道表达式的类型。
auto让编译器通过初始值推算变量的类型,auto定义的变量必须有初始值:
auto item = val1 + val2;
一条语句中声明多个变量,因为一条声明语句只能有一个基本数据类型,所以该语句中所有变量的初始基本数据类型都必须一样:
auto i=0 ,*p=&i; //正确:i是整数、p是整型指针
auto sz=0 , pi=3.14; //错误:sz、pi类型不一致。
decltype类型指示符
希望从表达式的类型推断出要定义的变量的类型:
编译器分析表达式得到它的类型,不实际计算表达式的值:
decltype(f()) sum = x; //sum的类型是f()函数的返回类型
warning: decltype((variable))的结果永远是引用,而decltype(variable)结果只有当variable本身就是引用时才是引用。
string
string对象会自动忽略开头的空白,并从第一个真正的字符读起,直到遇见下一处空白:
input " Hello World! "
output "Hello"
getline读取一整行:
int main()
{
string line;
//每次读入一整行,直至到达文件的末尾
while(getline(cin, line))
cout << line << endl;
return 0;
}
- 注:用getline得到的string最后不包含换行符。
- Tip:string的size方法返回类型string::size_type是unsigned,所以不要用int混淆了,因为比较s.size() < n(n是负数)时几乎都是True,n会转换为比较大的无符号值,
string和字符串字面值不是同一类,string相加至少有一个是string
string s2="world"; //s2是字面值“world”的副本
string s7 = ("hello" + ", ") + s2; //错误,字面值不能直接相加
处理字符
例子:统计string对象中的标点符号:
int main()
{
string s("Hello World!!!");
decltype(s.size()) punct_cnt = 0;
for (auto c : s)
if (ispunct(c))
++punct_cnt;
cout << punct_cnt
<< " punctuation characters in " << s << endl;
return 0;
}
处理部分字符
下标运算符[]接受输入参数string::size_type类型的值;返回值是该位置上字符的引用。
s[s.size()-1]
是最后一个字符。
vector
vector是一个类模板(class template)
实例化:
vector<int> ivec; //ivec保存int类型的对象
初始化:
vector<string> v1{"a", "an", "the"}; //列表初始化
vecotr<int> ivec(10); //10个元素,每个都是0
vector<int> ivec(10, -1); //10个元素,每个都被初始化为-1
Warning:范围for语句体内不应改变其所遍历序列的大小
v.push_back(t) //尾端添加值为t的元素
v.size()
v.empty()
例子:分数段统计:
//分数段统计
int main()
{
vector<unsigned> scores(11, 0); //11个分数段,全都初始化为0
unsigned grade;
while (cin >> grade) {
if (grade <= 100)
++scores[grade / 10];
}
return 0;
}
迭代器
迭代器提供对对象的间接访问。就迭代器而言,其对象是容器中的元素或者string对象中的字符。
*iter 返回迭代器iter所指元素的引用
iter->mem 解引用iter并获取该元素的名为mem的成员,等价于(*item).mem
迭代器类型
vector<int>::iterator it; //it能读写vector<int>的元素
vector<int>::const_iterator it3; //it3只能读元素,不能写元素
- 注:如果vector对象或string对象是一个常量,只能使用const_iterator。
- 如果对象只需读操作而无须写操作的话最好使用常量类型。
- cbegin、cend只返回const_iterator:
vector<int> v;
auto it3 = c.cbegin(); //it3的类型是vector<int>::const_iterator
int main()
{
string text;
getline(cin,text);
for ( auto it = text.cbegin(); it != text.cend() ; ++it)
cout << *it << endl;
return 0;
}
迭代器失效:任何一种可能改变vector对象容器的操作,如push_back
Warning:使用了迭代器的循环体,都不要想迭代器所属的容器添加元素。
计算最接近vi中间元素的一个迭代器
auto mid= vi.begin() + vi.size() / 2;
迭代器距离的类型:difference_type
迭代器运算:
#include "stdafx.h"
#include<string>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main()
{
string text;
getline(cin,text);
sort(text.begin(), text.end());
cout << text << endl;
char sought = 'a';
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;
}
cout << *mid<<endl;
return 0;
}
数组
数组的维度必须是一个常量表达式:
unsigned cnt = 42; //不是常量表达式
constexpr unsigned sz = 42; //常量表达式
string bat[cnt]; //错误。cnt不是常量表达式
tip:理解数组声明,从数组的名字开始由内向外的顺序阅读。
数组下标类型:size_t
指针
int ia[] = {0,1,2,3};
auto ia2(ia); //ia2是一个整型指针,指向ia的第一个元素
auto ia3(&ia[0]); //ia3类型是int* 与上面一样
decltype(ia) ia4 = {0,1,2,3}; //ia4是一个含有四个元素的整型数组
尾后指针,类似尾后迭代器:
int *e = &arr[10]; //指向arr尾元素的下一位置的指针
更安全的使用:标准库函数begin和end:
指针间的距离:
auto n = end(arr) - begin(arr); //arr中元素的数量
n的类型是内置的ptrdiff_t
类型
解引用
int a[] = {0, 2, 4, 6, 8};
int last = *(ia + 4); //解引用等价于: ia[4]
Warning:内置的下标运算符所用的索引值不是无符号类型。
表达式
位运算符
- 左移运算符 << : 向右侧插入值为0的二进制位;
- 右移运算符 >> : 1.左侧运算对象是无符号类型,在左侧插入值为0的二进制位,2.左侧运算对象是带符号类型,在左侧插入符号位的副本或值为0的二进制位。
- 位与运算符 & :
- 位异或运算符 ^ :
- 位或运算符 | :
整型提升:把小整数类型转换成较大的整数类型。
函数
Best Practice: 如果函数无需改变引用形参的值,最好声明为常量引用:
bool isShorter(const string &s1,const string &s2){
return s1.size() < s2.size();
}
引用形参返回额外的信息
string::size_type find_char(const string &s, char c, string::size_type &occurs) {
auto ret = s.size();
occurs = 0;
for (decltype(ret) i = 0; i != s.size(); ++i) {
if (s[i] == c) {
if (ret == s.size())
ret = i;
++occurs;
}
}
return ret;
}