文章目录
大多数编程语言都有此些基本特征: 内置类型、变量、表达式和语句、if或while等控制结构、函数
通常通过两种方式进一步补充其基本特征: 自定义类,从而实现语言的扩展;将一些有用的功能封装成库函数提供给程序员。
Python是动态性的,它会在程序运行的时候检查数据类型;与之相反,C++是一种静态数据类型语言,它的类型检查发生在编译时。因此,编译器需要知道每一个变量对应的数据类型。
2.变量和基本类型
2.1基本内置类型
- 算数类型: 整数数,浮点数,字符,布尔类型
- 类型转换
- 字面值常量(像"42", 一眼看上去就知道是什么类型的常量, 即为字面值常量)
- 当无符号数+有符号数时候,有符号数为转换为无符号数,其值等于该负数+无符号数的模(若为32位的int, 则为4294967296)
- 无符号数是不会小于0的,所以用于循环中要注意。
2.2变量
变量提供一个具名的,可供程序操作的存储空间.C++每个变量都有其数据类型,数据类型决定着变量所占内存空间的大小、布局方式、该空间能存储的值的范围,以及变量能参与的运算。“变量”和“对象”一般可以互换使用,实际在声明内置类型的变量时,其实如同在实例化一个对象。
- 变量定义
- 变量声明和定义的关系
- 标识符
- 名字的作用域
变量定义:
int value{0}
,C++11的新特性,可以用花括号括起来以初始化变量值,这种初始化形式被称为列表初始化,这有什么用呢:int value{3.14}
如此则会报错,它具有检查初始化值是否符合声明类型的功能。- 在函数体内部的内置类型将不被初始化
- 建议初始化每一个内置类型的变量,防止出错!!
变量声明和定义的关系:
- 声明使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义负责创建与名字关联的实体。
- 变量声明规定了变量的类型和名字,在这一点上定义与之相同。但是除此之外,定义还申请存储空间,也可能会为变量赋一个初始值。
extern int i;//声明而非定义
int i;//声明并且定义
extern int i = 2;//声明并且定义
- 注意:变量能且只能被定义一次,但是可以被多次声明
标识符:
用户自定义的标识符不能连续出现两个下划线,也不能以下划线紧连大写字母开头。此外,定义在函数体外部的标识符不能以下划线开头。
规范:
- 标识符要能体现实际含义
- 变量名一般用小写字母,如index
- 用户自定义的类名一般以大写字母开头,如Man
- 如果标识符由多个单词组成,则单词应有明显区分,如student_loan或studentLoan
名字的作用域:
- 建议:在对象第一次使用的地方附近定义它是一种好的选择,如此有益于找到变量的定义。
::reused
,如此::
可以调用全局作用域的变量
2.3复合类型
- 引用
- 指针
- 理解复合类型的声明
引用:
引用没有创建新的内存空间,引用即别名,只是取了个新名字来调用已存在的对象罢了。引用的类型必须与其所引用对象的类型一致。
指针:
- &为取址符,*为解引用符
- 指针与引用类似,也实现了对其他对象的间接访问。指针与引用不同:它本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内可以指向几个不同的对象;指针无须在定义时赋初值。
- 建议初始化所有的指针,并且在可能的情况下,尽量等定义了对象之后再定义指向它的指针。
void *pv
是哟中特殊的指针, 可用于存放任意对象的地址
指针的值(即地址)应属于下列4种状态之一:
- 指向一个对象
- 指向紧邻对象所占空间的下一个位置
- 空指针, 意味着指针没有指向任何对象
- 无效指针, 也就是上述情况之外的值
复合类型:
int* p1;
即int *p
,意思为: p1是指向Int的指针
指向指针的指针:
int ival = 1024;
int *pi = &ival;//pi指向一个int型的数
int **ppi = π//ppi指向一个int型的指针
int ***pppi = &ppi;//pppi指向一个int型的指针的指针
指向指针的引用:
int i = 42
int *p;
int *&r = p;//从右往左阅读r的定义。&r则r是一个引用;*&r则r引用的是一个指针;
//最后, 声明的int *&r指出r引用的是一个int指针
r = &i;//r引用了一个指针, 因此r赋值&i就是令p指向i
*r = 0;//解r即解p得到i,也就是p指向的对象, 将i值改为0
2.4const限定符
- 我们希望定义这样一种变量,它的值不能被改变。
- const对象一旦创建后其值就不能再改变,所以const对象必须初始化。
- 默认状态下,const对象仅在文件内有效,当多个文件出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量。若想要在多个文件中共享const对象,必须在变量的定义之前添加extern关键字
extern const int bufSize = fcn()//file1.cc
extern const int bufSize;//file1.h
- const的引用
- 指针和const
- 顶层const
- constexpr和常量表达式
const的引用
int i =42;
int &r1 = i;
const int &r2 = i;
r1 = 0; //正确
r2 = 0; //错误,r2是一个常量引用
指针和const
要想存放常量对象的地址,只能使用指向常量的指针:
const double pi = 3.14; //pi是个常量
double *ptr = π //错误!
const double *cptr = π //正确
*cptr = 42 //错误!
一个而指向常量的指针可以指向一个非常量对象(即不能通过该指针来改变指向对象的值,但是对象的值是可以通过其他途径改变的)
double dval = 3.14;
cptr = &dval
Tip: 所谓指向常量的指针或引用,不过是指针或引用“自以为是”罢了,它们觉得自己指向了常量,所以自觉地不去改变所指向对象的值。但是它们是可以通过其他途径改变的。
const指针:
指针是对象而引用不是,因此可以把指针本身定位常量。常量指针必须初始化,而且一旦初始化完成,则它的值(也就是存放指针中的那个地址)就不能在再改变了。
顶层const:
顶层const表示指针本身是个常量,底层const表示指针指的对象的一个常量
constexpr和常量表达式:
常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。
C++11新标准规定,允许如下声明来让编译器验证变量的值是否是一个常量表达式:
constexpr int mf = 20;
constexpr int limit = mf + 1;
一般来说,如果你认定变量是一个常量表达式,那就把它声明成constexpr
类型。常量表达式的值是"字面值常量"–包括算数类型、引用和指针。string不属于
在constexpr
声明中如果定义了一个指针,限定符cosntexpr
仅对指针有效,与指针所指的对象无关:
cosnt int *p = nullptr; //p是一个指向整型常量的指针
constexpr int *q = nullptr; //q是一个指向整数的常量指针
constexpr
会把它所定义的对象置为顶层const。
2.5处理类型
- 类型别名
- auto类型说明符
- decltype类型指示符
类型别名:
C++11规定了一种新的方法,使用别名声明来定义类型的别名:
using SI = double;
typedef char *pstring;
const pstring cstr = 0; // cstr是指向char的常量指针
要注意,const是对给定类型的修饰。pstring
实际上是指向char的指针,因此const pstring
就是指向char的常量指针,而非指向常量字符的指针。
auto类型说明符号:
C++11引入了auto类型说明符,auto让编译器通过初始值来推算变量的类型。显然auto定义的变量必须有初始值:
auto sum = x + y
还能够一条语句声明多个变量,该语句中所有变量的初识基本类型都必须一样:
auto i = 0, *p = &i; //正确
auto sz = 0, pi = 3.14 //错误
编译器推断出来的auto类型有时候和初始值的类型并不完全一样:
int i = 0, &r = i;
auto a = r; //a是一个整数而不是引用(r是i的别名,而i是一个整数)
其次, auto一般会忽略掉顶层const, 同时底层const则会保留下来:
const int ci = 0, &cr = ci;
auto b = ci; //b是一个整数(ci的顶层const特性被忽略掉了)
auto d = &i; //d是一个整型指针
auto e = &ci; //e是一个指向整数常量的指针(底层const会保留)
如果希望推断出的auto类型是一个顶层const, 需要明确指出:
const auto f = ci; //ci的推演类型是int, f是const int
还可以将引用的类型设为auto, 此时原来的初始化规则仍然适用,并且设置一个类型为auto的引用时, 初始值中的顶层常量属性仍然保留:
auto &g = ci; // g是一个整型常量引用
auto &h = 42 // 错误
const auto &j = 42; // 正确: 可以为常量引用绑定字面值
要在一条语句中定义多个变量,切记,符号&和*只从属于某个声明符号, 而非基本数据类型的一部分, 因此初始值必须是同一种类型:
auto k = ci, &l = i; // k是整数, l是整数引用
auto &m = ci, *p = &ci; // m是对整数常量的引用, p是指向整数常量的指针
auto &n = i, *p2 = &ci; // 错误: i的类型是int, 而ci的类型是const int
decltype类型指示符:
若遇到这种情况:希望从表达式的类型推断出要定义的变量类型,但是不想用该表达式的值初始化变量。C++11引入了第二种类型说明符decltype
, 它的作用是选择并返回操作数的数据类型。在此过程中, 编译器分析表达式并得到它的类型, 却不实际计算表达式的值:
decltype(f()) sum = x; // sum的类型就是函数f的返回类型
decltype
处理顶层const和引用的方式与auto有些许不同。如果decltype
使用的表达式是一个变量, 则dectype返回该变量的类型(包括顶层const 和引用在内):
const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x的类型是const int
decltype(cj) y = x; //y的类型是const int&, y绑定到x
decltype(cj) z; // 错误
如果decltype
使用的表达式不是一个变量,则decltype
返回表达式结果对应的类型。需要注意的是解引用操作, 解引用指针所得到的是一个引用:
int i =42, 8p = &i;
decltype(*p) c; // 错误!! c是int&, 必须初始化
还有一个要注意的, decltype
的表达式如果是加上了括号的变量, 结果将是引用
decltype((i)) d; // 错误: d是int&, 必须初始化
2.6自定义数据结构
- 定义Scales_data类型
- 使用Scales_data类
- 编写自己的头文件
从最基本的层面理解, 数据结构就是把一组相关的数据元素组织起来然后定义使用它们的策略和方法。C++允许用户以类的形式自定义数据类型。
struct Sales_data{...};
C++11新标准规定, 可以为数据成员提供一个类内初始值。
头文件:
#infdef SALES_DADTA_H // 当且仅当变量未定义时为真, 一旦检查结果为真, 则执行后续操作直至遇到endif
#define SALES_DATA_H
#inlcude <string>
...
#endif
第一次包含Sales_data.h时, #ifndef
检查结果为真, 运行直至#endif
. 后面如果再一次包含Sales_data.h, 则#iifndef
的检查结果为假.
3.字符串、向量和数组
第2章的内置类型是由C++语言直接定义的。标准库定义了另外一组具有更高性质的类型,它们尚未直接实现到计算机硬件中。本章介绍两种最重要的标准库类型: string和vector. string表示可变长字符序列, vector存放的是某种给定类型对象的可变长序列。 还有数组类型, 它和其他内置类型一样, 数组的实现与硬件密切相关, 但是相较于标准库的string和vector, 数组在灵活性上稍显不足。
3.1命名空间的using声明
头文件不应包含 using 声明
3.2标准库类型string
#include <string>
using std::string // 作为标准库的一部分, string定义在命名空间std中
- 定义和初始化string对象
- string对象上的操作
- 处理string对象中的字符
string对象上的操作:
一个类, 可以规定初始化对象的方式(如上所示), 还要定义对象上所能执行的操作。类既能够定义通过函数名调用的操作, 也能定义<<、+等各种运算符在类对象上的新含义。
cin >> s1
如此会忽略开头的空白并从第一个真正的字符开始读起,若想保留输入时的空白,应该用getline
函数代替原来的>>运算符。getlone
函数的参数是一个输入流和一个string对象,函数从给定的输入流中读入内容,直到遇到换行符为止(注意换行符也被读进来了),然后把所读的内容存入到那个string对象中去(注意不存换行符):
int main(){
string line;
while(getline(cin, line))
cout<< line << endl;
}
s.size()
返回的是一个string::size_type
类型的值,其是一个无符号类型的值而且能足够存放下任何string对象的大小。
string的大小比较:
- 如果两个string对象的长度不同,而且较短string对象的每个字符都与较长string对象对应位置上的字符相同,就说较短string对象小于较长string对象。
- 如果两个string对象在某些对应的位置上不一致,则string对象比较的结果其实是string对象中第一对相异字符比较的结果
字面值和string对象相加:
需注意的是,必须确保每个加法运算符两侧的对象至少有一个是string:
string s1 = "hey";
string s4 = s1 + " man"; // 正确
string s5 = "hello" + "man"; // 错误
切记,字符串字面值与string是不同的类型。
处理string对象中的字符:
在cctype
头文件中定义了一组标准库函数处理这部分工作:
想要输出每个字符:
for (auto c : s)
cout<< c << endl;
想要改变字符串内的字符:
for (auto &c : s)
c = toupper(c);
cout << s << endl;
只处理一部分字符:
一种是使用下标,另外一种是使用迭代器。
使用下标结合判断语句进行部分处理。
3.3标准库类型vector
标准库类型vector表示对象的集合,其中所有对象的类型都相同。集合中的每个对象都有一个与之对应的索引,索引用于访问对象。因为vector"容纳着"其他对象,所以它也常被称作容器。
C++语言既有类模板,也有函数模板,其中vector是一个类模板。只有对C++有了相当深入的理解才能写出模板。
模板本身不是类或函数。编译器根据模板创建类或函数的过程称为实例化,当使用模板时,需要指出编译器应该把类或函数实例化成何种类型,如vector:
vector<int> ivec; // ivec保存int类型的对象
vector<Sales_item> Sales_vec; // 保存Sales_item类型的对象
vector<vector<string>> file; // 该向量的元素是vector对象
- 定义和初始化vector对象
- 向vector对象中添加元素
- 其他vector操作
直接初始化适用于三种情况:初始值已知且数量较少、初始只是另一个vector对象的副本、所有元素的初始值都一样。
向vector对象中添加元素v.push_back
:
把一个值当成vector对象的尾元素push到vector对象的尾端(back)。
vector<int> v2; // 空vector对象
for (int i = 0; i != 100; ++i)
v2.push_back(i);
建议vector的定义不需设定其大小,因为vector对象能高效增长。
范围for语句体内不应改变其所遍历序列的大小。所以不适合在范围for语句内使用vector。
其他vector操作:
v,size()
返回的是由vector定义的size_type类型, 如vector<int>::size_type
。- 只有当元素的值可比较时,vector对象才能被比较。
- vector同样可以利用下标进行索引。但是不可以用下标形式添加元素,只能通过push_back。
3.4迭代器介绍
所有标准库容器都可以使用迭代器,但是其中只有少数几种才同时支持下标运算符。类似于指针类型,迭代器也提供了对对象的间接访问。使用迭代器可以访问某个元素,迭代器也能从一个元素移动到另外一个元素。
迭代器有有效和无效之分。有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一位置;其他所有情况属于无效。
- 使用迭代器
- 迭代器运算
使用迭代器:
有迭代器的类型同时拥有返回迭代器的成员。比如,都有begin
和end
成员:
beigin
返回第一个元素的迭代器,end
返回指向容器"尾元素的下一位置"
auto b = v.begin(), e = v.end();
若begin
和end
返回的是同一个迭代器,则容器为空。
迭代器的迭代:
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
*it = toupper(*it)
C++程序员习惯性地使用!=, 其原因和它们更愿意使用迭代器而非下标的原因一样:因为这种编程风格在标准库提供的所有容器上都有效。
迭代器的类型有两种:
第一种可以对对象进行读写,第二种则为常量指针,能读取但不能够修改它。
vector<int>::iterator it;
vector<int>::const_iterator it2;
为了专门得到const_iterator
类型的返回值,C++11新标准引入了两个新函数,分别是cbegin
和cend
:
auto it3 = v.cbegin()
如指针和引用一般,常量指针或引用可以指向非常量。不论vector对象本身是否是常量,返回值都是const_iterator
结合解引用和成员访问操作:
C++语言定义了箭头运算符(->)。箭头运算符把解引用和成员访问两个操作结合在一起,也就是说,it->mem
和(*it).mem
表达的意思是相同的。
谨记,但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
迭代器运算:
配合这些规则可以实现许多数据结构及算法,如链表等…
一个定点算法是二分搜索。二分搜索从有序序列中寻找某个给定的值。二分搜索从序列中间的位置开始搜索,如果中间位置的元素正好就是要找的元素,搜索完成;如果不是,假如该元素小于要找的元素,则在序列的后半部分继续搜索;加入该元素大于要找的元素,则在序列的前半部分继续搜索。在缩小的范围中计算一个新的中间元素并重复之前的过程,直至最终找到目标或者没有元素可供继续搜索。
int binary_search(vector<int> text, int sought){
// test必须是有序的
// beg和end表示搜索的范围
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;
}
return *mid;
}
3.5数组
- 定义和初始化内置化数组
- 访问数组元素
- 指针和数组
- C风格字符串
- 与旧代码的接口
定义和初始化内置化数组:
- 初始化的维度必须是一个常量表达式
unsigned cnt = 42;
constexpr unsigned sz = 42;
int arr[10]; // 有10个整数的数组
int *parr[sz]; // 含有42个整型指针的数组
string bad[cnt]; // 错误: cnt不是常量表达式
定义数组的时候必须指定数组的类型,不允许用auto关键字由初始值的列表推断类型。另外和vector一样,数组的元素应为对象,因此不存在引用的数组。
- 显示初始化数组元素:
int a2[] = {0, 1, 2};
int a3[5] = {0, 1, 2}; // 等价于{0, 1, 2, 0, 0}
int a5[2] = {0, 1, 2} // 错误!!初始值过多
- 字符数组的特殊性:
要注意,若用字符串字面值初始化数组,要注意字符串字面值的结尾处还有一个空字符。
char a[] = "C++"; //维度为4
- 不允许拷贝和赋值:
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个整数的数组
默认情况下,类型修饰符从右向左依次绑定。对于ptrs来说,:首先我们定义的是一个大小为10的数组,它的名字是ptrs, 然后知道数组中存放的是指向int的指针。
但是对于Parray的理解就难了,所以对数组而言,由内向外阅读比从右向左好多了。首先(*Parray)意味着Parray是个指针,接下来观察右边,可知道Parray是个指向大小为10的数组的指针,最后观察左边,知道数组中的元素是int。
arrRef也是,(&arrRef)意味着arrRef是个引用,引用的对象是一个大小为10的数组,数组中元素的类型是int。
int *(&array)[10] = ptrs; // arry是数组的引用,该数组含有10个指针
访问数组元素:
也可以这样访问
for (auto i : scores)
cout << i << " ";
因为系统指导数组scores中有多少个元素,使用范围for语句可以减轻人为控制遍历过程的负担。
数组的下标是否在合理范围之内也是经常出现的bug…所以要小心。
指针和数组:
数组的元素也是对象,因此像其他对象一样,对数组的元素使用取地址符就能得到指向该元素的指针:
string nums[] = {"one", "two", "three"};
string *p = &nums[0]; // p指向nums的第一个元素
++p; // 指针也是迭代器,p指向nms[1]
string *p2 = nums; // 自动指向首元素,相当于p2 = &nums[0]
auto与dectltype在数组的区别
int ia[] = {0, 1, 3}
auto ia2(ia); // ia2是一个整型指针, 指向ia的第一个元素
decltype(ia) = ia3 = {1, 2, 5}; // ia3是一个含有3个整数的数组
标准库函数begin和end
因为数组不是类类型,因此这儿两个函数不是成员函数。正确的使用形式是将数组作为它们的参数,注意:尾后指针不能执行引用和递增操作:
int ia[] = {0, 1, 3};
int *beg = begin(ia); // 指向ia首元素的指针
int *last = end(ia); // 指向ia尾元素的下一位置的指针
指针可以相减,得到的结果类型为ptrdiff_t
的标准库类型,是一种带符号的类型。
对数组执行下标运算其实是对数组元素的指针执行下标运算:
int i = ia[2]; // ia转换成指向数组首元素的指针,ia[2]得到(ia + 2)所指的元素
int *p = ia; // p指向ia的首元素
i = *(p + 2); // 等价于i = ia[2]
int *p = &ia[2];
int j = p[1]; // 相当于*(p+1), 就是ia[3]表示的那个元素
int k = p[-2]; // ia[0]
所示说数组内置的下标运算符所用的索引值不是无符号类型,这点与string和vector不一样。
用数组初始化vector:
int arrr[] = {0, 1, 2, 3, 4, 5};
vector<int> ivec(begin(arr), end(arr));
建议:尽量使用标准库类型而非数组。现代的C++程序应当尽量使用vector和迭代器,避免使用内置数组和指针;应该尽量使用string,避免使用C风格的局域数组的字符串。
3.6多维数组
严格来说,C++语言中没有多维数组,通常所说的多维数组其实是数组的数组。
4.表达式
表达式由一个或多个运算对象组成,对表达式求值将得到一个结果。字面值和变量是最简单的表达式,其结果就是字面值和变量的值。把一个运算符和一个或多个运算对象组合起来可以生成较复杂的表达式。
4.1基础
- 基本概念
- 优先级与结合律
- 求值顺序
基本概念:
C++定义了 一元运算符 和 二元运算符 。作用于一个运算对象的运算符就是一元运算符,如&
, 解引用符*
;作用于两个运算对象的就是二元运算符,如==
, 乘法运算符*
。还有三元运算符。函数调用也是一种特殊的运算符,它对运算对象的数量没有限制。
- 组合运算符和运算对象的求值顺序
- 运算对象转换
- 重载运算符(对于类类型的运算对象可自行定义,但是运算符的优先级和结合律无法改变)
- 左值和右值(当一个对象被用作右值时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置))
只有4中运算符明确规定了运算对象的求值顺序:
逻辑与(&&)运算符,它规定先求左侧对象的值,只有当左侧运算对象的值为真时才继续求右侧运算对象的值。逻辑或(||)运算符、条件(?:)运算符、逗号(,)运算符。
经验准则:
- 拿不准的时候最好用括号来强制让表达式的组合关系复合程序逻辑的要求。
- 如果改变了某个运算对象的值,在表达式的其他地方不要再使用这个运算对象。(*++iter是个例外,它需要先递增再解引用)
4.2算术运算符
4.3逻辑与关系运算符
对于这两类运算符来说,运算对象和求值结果都是右值。
4.4赋值运算符
赋值运算符的左侧对象必须是一个可修改的左值。
赋值运算符满足右结合律,这一点与其他二元运算符不太一样:
int ival, jval;
ival = jval = 0; // 正确,都被赋值为0
因为赋值运算符的优先级低于关系运算符的优先级,所以在条件语句中,赋值部分通常应该加上括号。
复合赋值运算符
4.5递增和递减运算符
++i; // 前置版本得到递增之后的值
i++; // 后置版本得到递增之前的值
一条语句中混用解引用和递增运算符:
*ptr++; // 把ptr的值加1,然后返回ptr的原始值的副本作为其求值结果
大多数运算符都没有规定运算对象的求值顺序,因为递增和递减运算符会改变运算对象的值,所以要提防在复合表达式中错用这两个运算符。
如*beg = toupper(*beg++)
就会模棱两可
4.6成员访问运算符
解引用运算符的优先级低于点运算符。
4.7条件运算符
条件运算符(?:
)允许我们把简单的if-else逻辑嵌入到单个表达式当中
cond ? expr1 : expr2;
conda为真则对expr1求值并返回该值,否则对expr2求值并返回该值。
可以嵌套条件运算符:
cond ? expr1 : cond2 ? expr2 : pxpr3;
如此代码可读性会下降,最多嵌套两层。
4.8 位运算符
位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。位运算符提供检查和设置二进制位的功能。一种名为bitest的标准库类型也可以表示任意大小的二进制位集合,所以为运算符同样能用于bitest类型。
4.9sizeof运算符
sizeof运算符返回一条表达式或一个类型名字所占的字节数。其有两种形式:
sizeof (type)
sizeof expr
第二种形式中,sizeof返回的是表达式结果类型的大小。sizeof并不实际计算其运算对象的值。
4.10逗号运算符
逗号运算符含有两个运算对象,按照从左向右的顺序依次求值。和逻辑与、逻辑或以及条件运算符一样,逗号运算符也规定了运算对象求值的顺序。
(expr1, expr2)
首先对左侧的表达式求值,然后将求值结果丢弃掉,逗号运算符表达式真正的结果是右侧表达式的值。
4.11类型转换
- 算术转换
- 其他隐式类型转换
- 显式转换
小整型会自动提升成大整型。
4.12运算符优先级表
5.语句
5.1简单语句
空语句:
语法上需要一条语句,但是逻辑上不需要
while (cin >> s && s!= sought)
;
别漏写分号,也别多写分号:
像这样就要出事咯:
while(iter != s.end());
++iter;
复合语句:
复合语句是指用花括号括起来的语句和声明的序列。复合语句也被称作块。一个块就是一个作用域。
while(val <= 10){
sum+=val;
val++;
}
5.2语句作用域
5.3条件语句
if语句:
else与最近的if搭配,如果不想这样,可以用花括号{}将不想被else匹配的if语句括起来,使其成为上一层if的复合语句。
switch语句:
用以与固定的值做比较
不要忘记break
case 'a':
++Cnt;
break;
case 'a': case 'e': case 'i': case'o': case'u':
++vowCnt;
break;
default:
++other;
break;
5.4迭代语句
- while语句
- 传统的for语句
- 范围for语句
- do while语句
传统的for语句:
for (init-statement; condition; expression)
statement
- 其中init-statement必须是三种形式中的一种: 声明语句(可以一个声明 多个对象)、表达式语句或者空语句
- condition为空的话则一直为true, 所以此时statement中必须有语句负责退出循环
- expression也可以为空, 如此就要求循环体或者条件部分有改变迭代变量的值
范围for语句:
C++11引入了一种更简单的for语句,这种语句可以遍历容器或其他序列的所有元素,形式为:
for (declaration : expression)
statement
- expression表示的必须是一个序列,这些类型的共同特点是拥有能返回迭代器的begin和end成员。
- declaration定义一个变量,序列中的每个元素都能转换成变量的类型,最方便的就是用auto类型,若是想要修改序列中的元素则应声明成引用类型。
- 所以不能通过范围for语句增加vector对象的元素,因为范围for语句中预存了end()的值。
do while语句:
do
statement
while(condition);
先执行再判断
5.5跳转语句
C++包含四中跳转语句: break continue goto和return.这里只介绍前三种
break语句:
break语句负责终止离它最近的while, do while, for或switch语句,并从这些语句之后的第一条语句开始继续执行。
continue语句:
continue语句终止最近的循环中的当前迭代并立即开始下一次迭代。对于for循环来说,是继续执行for语句头的expression
goto语句:
goto语句作用是从doto语句无条件转到同一函数内的另一条语句。
不要在程序中使用goto语句,因为它使得程序即难理解又难修改
goto label;
label是用于标识一条语句的标识符,形式如下:
end : return;
goto语句若跳回到变量定义之前意味着系统将销毁该变量,然后重新创建它:
begin:
int sz = get_size();
if (sz <= 0){
goto begin;
}
5.6try语句块和异常处理
异常是指存在于运行时的反常行为,这些应为超出了函数正常功能的范围。典型的异常包括失去数据库连接以及遇到意外输入等。处理反常行为可能是设计所有系统最难得一部分。
只是用于检测,无需知道故障要在何处得到解决。
异常处理机制为程序中异常检测和异常处理两部分的协作提供支持。C++中的异常处理包括:
- throw表达式,异常检测部分使用throw表示它遇到了无法处理的问题。
- try语句块,异常处理部分使用try语句块处理异常。try语句块以关键字try开始,并以一个或多个catch子句结束。try中代码抛出的异常通常会被某个catch子句处理。
- 一套异常类,用于在throw表达式和相关的catch子句之间传递异常的具体信息。
throw表达式:
throw表达式包含关键字throw和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型。
if (item1.isbn() != item2.isbn())
throw runtime_error("Data must refer to same ISB");
如果ISBN不一样就抛出一个异常,该异常是类型runtime_error的对象。抛出异常将终止当前的函数,并把控制权转移给能处理该异常的代码。
runtime_error是标准库异常类型中的一种。
try语句块:
try{
program-statements
} catch (expression-declaration){
handler-statements
}catch (expression-declaration){
handler-statements
}
while (cin >> item1 >> item2){
try {
if (item1.isbn() != item2.isbn())
throw runtime_error("Data must refer to same ISB");
}catch (runtime_error err){
cout << err.what()
<< "\nTry Again? Enter y or n" << endl;
char c;
cin >> c;
if (!cin || c=='n')
break;
}
}
若这个try语句块中的catch不能够处理该异常,则会跳转到外层try函数块继续查找。
标准异常: