如有问题欢迎指出☺
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
提示:以下是本篇文章正文内容,下面案例可供参考
四,表达式
一元运算符,二元运算符,三元运算符
运算符的求值顺序没有明确规定,给编译器优化留下了余地
表达式要不是左值rvalue要不是右值lrvalue
逻辑与(&&),当左边为真时,再看右边;左边为假,输出false
逻辑或( || ), 当左边为真是,不看右边直接输出true ,当左边为false时再看右边
条件(?😃
逗号( , )
左值有地址可以变,右值没有地址不可以改变不可以赋值。
求值顺序 ???
优先级与结合律没有规定函数的调用顺序,f() + g() * h() + j()
若四个函数时不相干的则无所谓,若是相关的则会产生未定义的行为
//下面的运算将产生未定义错误 ??? 是否和编译器相关?
int i = 1;
cout << i << " " << ++i << endl; // 对于先算左边的 i 还是先算右边的 ++i 没有明确定义
// 因此不论编译器生成什么样的代码程序都是错误的
有4种运算符明确规定了运算对象的求值顺序:
&& || ?: ,
求值顺序、优先级、结合律括号无视优先级与结合律
算数运算符(左结合律:当优先级相同时,按照从左向右的顺序进行组合)
运算符 | 功能 | 用法 | |
---|---|---|---|
+ - | 一元正号 一元负号 | +expr -expr | |
* / % | |||
+ - | 加法 减法 | ||
一元运算符的优先级最高, | |||
算数运算符的运算对象和求值结果都是右值 |
计算商或者余数时, C++11规定商一律向0取整
a ➗ b = q + r //c++中q向0的方向取
所以:
m%(-n) = m%n
(-m)%n = -(m%n)
思考:如果在运算过程中数值溢出了怎么办
short sv = -3, sv3 = sv/0.1; //为什么sv3的结果是 -29 ???
逻辑和关系运算符
优先级 | 运算符 |
---|---|
高 | ! |
< <= > >= | |
== != | |
&& | |
低 | || |
赋值运算
先对右边进行计算,然后把计算结果赋给左边,赋值运算的结果为左边的值
右边的类型转换成左边的类型
//初始化,而非赋值
int i = 0, j = 0 , k = 0;
const int ci = i;
++i = j++ = 9; //❌
++i = ++j = 9; //√ 意义不大,结果同下
i = j = 9; //√ 右结合律(从右向左算)
int i;
while ((i = get_value()) != 42 ){i;}
建议这样写if (42 == i)
++i;
* p ++ 等价于 * (p ++)
???
运算对象可按任意顺序求值
大多数运算符都没有规定运算对象的求值顺序,在一般情况下不会有什么影响。然而,如果一条子表达式改变了某个运算对象的值,另一条表达式又要使用该值,顺序就关键了
//该循环的行为是未定义的!
while(beg != s.end() && !isspace(*beg))
*beg = toupper(*beg++); //错误:该赋值语句未定义
成员访问运算符
ptr -> mem 等价于 (*ptr).mem
优先级比 * 高
运算符优先级:
点和箭头同优先级,高于递增和解引用
递增优先级高于解引用
条件运算符
优先级相当低
右结合律是什么意思? P161 4.7练习题
//cond ? : expr1 : expr2;
//当两个表达式都是左值或能转换成同一种左值类型时,运算结果是左值;否者运算结果是右值
string a = (grade < 60)? "fail" : "pass";//条件为true则只执行expr1,若为false则只执行expr2
位运算符(左结合律)
没太搞明白
python和c++一样
运算符 | |
---|---|
~ | 取反 |
<< >> | 移位 |
& | 位与 |
^ | 位异或 |
| | 位或 |
符号位的位运算依赖于机器
将非int 型提升为 int 再进行位操作
sizeof运算符
右结合律,与解引用运算符同级别
sizeof ( type )
sizeof expr
Sales_data data, *p;
sizeof(Sales_data); //存储Sales_data类型的对象所占的空间大小
sizeof data; //data的类型的大小,即sizeof(Sales_data)
sizeof p; //指针所占空间的大小
//相当于 sizeof ( * p )
sizeof *p; //p所指类型的空间大小,即sizeof(Sales_data)
sizeof data.revenue; //Sales_data的revenue成员对应类型的大小
sizeof Sales_data::revenue; //另一种获取revenue大小的方式
逗号运算符
赋值运算符优先级高于逗号运算符
从左向右依次求值,如果右侧运算对象是左值,那么最终结果也是左值
vector<int>::size_type cnt = ivec.size();
for (vector<int>::size_type ix=0; ix!=ivec.size(); ++ix, --cnt)
ivec[ix] = cnt;
语句
简单语句
表达式的作用是执行表达式并丢弃掉求值结果
int i=0;
; // 空语句
{} // 空块(block),一般块后不用加分号
// 一个块就是一个作用域
// 把逻辑上的多条语句转变成一个块
for(;;){ expr1; expr2;}
// v1
while (val <= 10){
sum += val;
++ val ;
}
//v2
while (sum += val, ++val, val <= 10)
; //空语句
语句作用域
while(int i = get_num()) { //每次迭代时创建并初始化i
cout << i << endl;
}
i = 0; //错误:再循环外部无法访问i
if else
//你以为的
if(true)
if (true)
expr1;
else
expr2;
// 程序实际上
if(true)
if (true)
expr1;
else
expr2;
//所以任何情况下必须有写花括号的习惯
if(true){
if(true){
expr1;
}
}else{
expr2;
}
//else if
if (true){
;
}else if (true){
;
}else{
;
}
switch语句
如果某个case标签匹配成功,将从该标签开始往后顺序执行所有case分支,除非有显示终止
switch(expr){
case label1L1(整型常量表达式):
expr;
break;
case label2(整型常量表达式):
expr;
break;
}
//
switch(expr){
case label1L1(整型常量表达式):
expr;
break;
case label2(整型常量表达式):
expr;
break;
default:
;
}
//内部变量定义
case true:
//搞一个块
{
string file_name = get_file_name();
}
break; //break放在花括号内或外都无所谓,主要为了方便阅读
case false:
if (file_name.impty()) //❌,file_name不在作用域之内
迭代语句
while(condition){ // condition可以为表达式或一个带初始化的变量声名
;
}
for (;/*条件为空,永远为true*/;){}
范围for语句
for (declaration : expression) //expresion是“可迭代的”
statment //如果要改变expression中的值declaration要是引用类型
do while 语句
do{
expr1;
expr2;
}while(condition);
break语句
终止离它最近的 while,do while,for或switch语句,并从这些语句之后的第一条语句开始继续执行
continue
终止本次迭代,立即进入下次迭代(最近的for,while和do while循环)
goto语句
不要在程序中使用它!!!
try语句块和异常处理
throw runtime_error("Data must refer to same ISBN");
try{
expr;
}catch(exception-declaration){
handler-statements;
}catch(exception-declaration){
handler;
}
函数
基础
// 返回类型,函数名,(0个或多个形参),{语句块/函数体}
int fact(int val){
int res=1;
while(val > 1){
res *= val --;
}
return res;
}
int main(){
int j = fact(6);
return 0;
}
函数调用
- 用实参初始化函数对应的形参
- 将控制权转移给被调用函数
形参和实参
函数的形参列表
//两个等价的写法
void f1(){} //没有形参
void f2(void){} //没有形参,与C兼容
int f3(int v1, int v2){} //correct
函数的返回类型
函数没有返回值用void
返回类型不能是数组,类型或函数类型,但可以是指向数组或函数的指针。
局部对象
名字有作用域,对象有生命周期
在所有函数体之外定义的对象存在于程序的整个执行过程中。此类对象在程序启动时被创建,至到程序结束才会销毁。局部变量的生命周期依赖于定义的方式。
自动对象
只存在于块执行期间
局部静态对象
flow-of-contral经过变量定义时初始化,之后一直存在至到程序终止才被销毁
size_t count_call(void){ // void时可选项
static size_t ctr = 0; // 调用结束后,这个值仍然有效
return ++ctr;
}
int main(){
for (size_t i = 0;i != 10;++i){
cout << count_call() << endl;
}
return 0;
}
函数声名
函数的名字必须在使用之前声名,可声名多次但只能定义一次。
函数三要素(返回类型、函数名、形参类型)表述了函数的接口,说明了调用函数所需的全部信息。
函数声名也称作函数原型
void print(vector<int>::const_iterator beg, vector<int>::const_iterator end);
//这样也可
void print(int&, int*);
分离式编译
参数传递
当形参是引用传递时,我们说它对应的实参被引用传递或者函数被传传引用调用。
当实参的值被拷贝给形参时,形参和实参时两个相互独立的对象,实参被值传递或函数被值传调用
传值参数
指针形参
拷贝的是指针的值,拷贝之后两个指针是不同的指针。指针指针使我们可以间接的访问它所指的对象。
传引用参数
使用引用避免拷贝
//如果函数无需改变引用形参的值,最好将其声明为常量引用
bool isShorter(const string &s1, const string &s2){
return s1.size() < s2.size();
}
int sumf(int a, int b=10, int c=20){
return a+b+c;
}
sumf(1,2);
使用引用形参返回额外信息
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; //记录c第一次出现的位置
}
++occurs; //将出现的次数+1
}
}
return ret; //出现次数通过occurs隐式地返回
}
不同的函数:函数名不同,或者,函数名相同但形参列表有明显不同
无法重载仅按返回类型区分的函数
void fcn(const int i){/*fcn能够读取i,但是不能向i写值*/}
void fcn(int i){/*...*/} //❌从夫定义fcn(int)这不叫“形参列表有明显不同”
尽量使用常量引用
数组形参
无法以值传递的方式使用数组参数
void print(const int *);
void print(const int []); //函数的意图是作用于一个数组
void print(const int [10]); //这里的维度表示我们期望数组含有多少元素,实际不一定
//如何确定数组的长度
//使用标记指定数组的长度
while(*cp){ //只要指针所指的字符不是空字符
cout << *cp++; //输出当前字符串并将指针+1
}
//使用标准库规范
void print(const int *beg, const int *end){
while (beg != end){ cout << *beg++ << endl; }
}
//显示传递一个表示数组大小的形参
void print(const int ia[], size_t size){
for (size_t i=0; i != size; ++i){
cout << ia[i] << endl;
}
}
print(j, end(j)-begin(j) );
//数组引用形参
void print(int (&arr)[10]){
for (auto elem : arr){ cout << elem << endl; }
}
print(k);//int k[10] = {1,2}
传递多维数组
void print(int (*matrix)[10], int rowSize) {/*...*/}
//等价定义
void print(int matrix[][10], int rowSize) {/*...*/}
main: 处理命令行选项
含有可变形参的函数
initializer_list形参:实参类型都相同,是一种标准库类型,用于表示某种特定类型的值的数组
(有点像元组)
void error_msg(initializer_list<string> il){
for (auto beg = il.begin(); beg != il.end(); ++beg)
cout << *beg << " ";
cout << endl;
}
if (){
error_msg({"functionX", expected, actual});
}else{
error_msg({"functionsX", "okay"});
}
返回类型和return
值是如何被返回的
返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。
string make_plural(size_t, const string &word, const string &ending){
return (ctr > 1) ? word+ending : word;
}
该函数的返回类型是string,意味着返回值将被拷贝的调用点。因此,该函数将返回word的副本或者一个未命名的临时string对象,该对象的内容是word和ending的和。
如果函数返回引用,则该引用仅是它所引对象的一个别名。
const string & shorterString(const string & s1, const string &s2){
return s1.size() <= s2.size() ? s1 : s2;
}
其中,形参和返回类型都是const string的引用,不管是调用函数还是返回结果都不会真正拷贝string对象。
不要返回局部对象的引用或指针
函数完成后,它所占用的存储空间也随之被释放掉,因此,函数终止意味着局部变量的引用将指向不再有效的内存区域:
//严重错误
const string & manip(){
string ret;
if (!ret.empty) return ret; //❌返回局部对象的引用
else return "Empty"; //❌“Empty”是一个局部临时量
}
要想确保返回值安全,我们不妨提问:引用所引的或指针所指的是在函数之前已经存在的哪个对象?
返回类类型的函数和调用运算符
调用运算符左结合律
auto sz = shorterString(s1, s2).size();
int sumf(int);
auto fname = sumf;
cout << fname(23) //合法
引用返回左值
调用一个返回引用的函数得到左值,其他返回类型得到右值。
char & get_val(string &str, string::size_type ix){
return str[ix]
}
string s{"a value"};
get_val(s, 0) = 'A'; //将s[0]的值改为 ‘A
函数返回类型是指针
int * sumf(int &a){
return &a;
}
int i1=5;
auto a = sumf;
*a(i1) = 1000;
cout << i1; //输出为1000
cout << *a(i1); //输出为1000
列表初始化返回值
vector<string> process(){
//...
// expected and actual is string object
if (expected.empty()){
return {};
}else if (expected == actual){
return {"functionX", expected, actual};
}
}
int sumf(int a, int b){
return {a};
}
int i1=5, i2=4, i3 = sumf(i1, i2);
cout << i3;
主函数main的返回值
递归
int factorial(int val){
if (val > 1){
return factorial(val-1) * val;
}
return 1;
}
main函数不能调用它自己
int & get(int * arr, int idx){ return arr[idx]; }
void printarr(int * arr, size_t sz){
if (sz > 10) return;
printarr(arr, sz + 1);
cout << arr[sz] << endl;
}
int main(){
int a[10];
for (int i = 0; i != 10; ++i){
get(a, i) = i;
}
printarr(a, 0);
}
返回数组指针
数组不能被拷贝
使用using比typedef更好理解
typedef int arrT[10]; //arrT是一个类型别名,它表示的类型是含有10个整数的数组
using arrT = int[10]; //arrT的等价声名
arrT*func(int i); //func返回一个指针,该指针指向一个含有10个元素的数组
指针就是数组 和 指针指向 数组的指针
int arr[10]; //arr是数组,数组名也是指针
int *p1 = arr; //数组名就是地址,用地址初始化指针
int *p2[10]; //p2是一个包含是个元素的数组,每个元素的类型是int*
int (*p3)[10] = &arr; //p3是一个指针,它指向包含十个元素的数组(数组也是指针)
//这里p3实际上是**类型,p3是一个指针,它指向另一个指针(另一个指针也是数组)
int ( *func(int i) )[10]; //如何理解
使用尾置返回类型
auto finc(int i) -> int ( * ) [ 10 ];
使用decltype
int odd[] = {1,3,5,7,9};
int even[] = {0,2,4,6,8};
decltype(odd) * arrPtr(int i){
return (i % 2) ? & odd : & even; //返回一个指向数组的指针
}
6.3.3节练习题目,函数返回值,搜索标记
返回数组的引用,这个数组包含10个元素,元素类型为string
string (&func(string (&s)[10]) )[10]{
return s;
}
用using
简单好用易理解
using another = string(&)[10];
another func(string (&s) [10]){ return s; }
//或者
another func(another s){ return s ;}
使用尾置
也还不错
auto func(string (&s)[10]) -> string(&)[10] {return s;}
用decltype
这个就差点意思了
decltype就是把变量的名字拿起,剩下变量的类型
string s[10]{"sdf","sffff"}, (&refs)[10] = s;
decltype(s) & func(decltype(s) &s){return s;}
//或者 一步到位
decltype(refs) func(decltype(refs) s){return s;}
最后在附上最难用的typedef
typedef string another1[10]; //等价于using another = string[10]
another1 &func( string(&s)[10] ){return s;}
//进一步的
typedef another1 &another2; //等价于using another = string(&)[10]
//就不太好用,感觉不如using舒服
another2 func( string(&s)[10] ){return s;}
//一步到位
typedef string (&another)[10];
another func(another s){return s;} //好像和using差不多,但using更直观
函数重载
如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数。
main函数不能重载
重载和const形参
一个拥有顶层const的形参无法和另一个没有顶层const的形参区分
顶层const不行
//
Recode lookup(Phone);
Recond lookup(const Phone); //❌重复定义
Recode lookup(Phone*);
Recond lookup(Phone* const); //❌重复定义
另一方面,如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的:
//都是新函数
Record lookup(Account&);
Record lookup(const Account &);
Recond lookup(Account *);
Recond lookup(const Account *);
const_cast和重载
const string & shorterString(const string &s1, const string &s2){
return s.size() <= s2.size() ? s1 : s2;
}
string & shorterString(string &s1, string &s2){
auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
return const_cast<string&>(r); //将const string &类型转化成string &
}
重载与作用域
note:在C++中,名字找查发生在类型检查之前
特殊用途语言特性
默认实参
按位置解析
默认实参声名
string screen(sz, sz, char = ' ');
string screen(sz, sz, char = '*'); //❌不能改变已经声名了的默认形参的默认值
string screen(sz = 24, sz = 80, char); //✔
内联函数
内联函数可以避免函数调用的开销
inline const string & shorterString(const string &s1, const string &s2){
return s1.size() <= s2.size() ? s1 : s2;
}
cout << shorterString(s1, s2) << endl;
//在编译过程中展开成类似于下面的形式
cout << (s1.size() < s2.size() ? s1 : s2) << endl;
constexpr函数
需要遵循的约定:
- 函数的返回类型及所有形参的类型都得是字面值类型
- 函数体中必须有且有一条return语句:
constexpr int new_sz() {return 42;}
constexpr int foo = new_sz(); //✔ foo是一个常量表达式
调试帮助
#include <cassert>
assert 预处理宏
assert (expr);
预处理名字由预处理器而非编译器管理。
NDEBUG 预处理变量
如果
#define NDEBUG
将不执行assert预处理宏
#ifndef NDEBUG
…
#endif
函数匹配
只可意会难以言传
指针函数
函数返回一个指针,该指针指向一个函数
bool lengthCompare(const string &, const string &); //声名一个函数
bool (*pf)(const string &, const string &); //声名一个指向函数的指针,未初始化
//使用函数指针
pf = lengthCompare; //pf指向名为lengthCompare的函数
pf = &lengthCompare; //等价赋值语句,取地址符是可选的
//函数指针无需解引用
bool b1 = pf("kl", "bjlks"); // 函数调用
bool b2 = (*pf)("asdf", "sdf"); // 等价调用
bool b3 = lenghtCompare("sd","ss");// 等价调用
string::size_type sumLength(const string&, const string&);
bool cstringCompare(const char*, const char*);
pf = 0; //✔
pf = sumlength; //❌返回类型不匹配
pf = cstringCompare; //❌形参类型不匹配
pf = lengthCompare; //✔函数和指针的类型精确匹配
重载函数指针
函数指针形参
返回指向函数的指针
将auto 和decltype用于函数指针
函数类型和指针类型
6.7节练习
int func(int a, int b){
return a+b;
}
int func2(int a, int b){
return a-b;
}
int func3(int a, int b){
return a*b;
}
int func4(int a, int b){
return a/b;
}
int main(){
vector<decltype(func)*> vac={func, func2, func3, func4};
for (auto c: vac){
cout << c(6,6) << endl;
}
}