primer c++ 5 chapter4表达式,5语句,6函数

如有问题欢迎指出☺


前言

提示:这里可以添加本文要记录的大概内容:


提示:以下是本篇文章正文内容,下面案例可供参考

四,表达式

一元运算符,二元运算符,三元运算符
运算符的求值顺序没有明确规定,给编译器优化留下了余地
表达式要不是左值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函数
需要遵循的约定:

  1. 函数的返回类型及所有形参的类型都得是字面值类型
  2. 函数体中必须有且有一条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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值