C++学习3 表达式、语句和函数拾遗
本笔记并非对知识点的梳理,只是建立在回顾基础上上的重申和查缺补漏。
表达式
表达式由一个或多个运算对象构成,对表达式求值可得到结果。字面值(常量)和变量是最简单的对象。可以运用运算发将运算对象进行组合得到更为复杂的表达式。
运算符
包括一元运算符和二元运算符。
重载运算符
C++语言定义了内置类型和复合类型的运算符操作,但是对于用户自定义的类并没有定义运算符,因而用户可以对运算符进行自己的定义,这种定义得到的运算符被称为重载运算符(overloaded operator)。
左值和右值
C的表达式为左值(lvalue)或右值(rvalue)中的一种。
在C中,当一个对象被用作右值的时候,用的是对象的值(内容);当一个对象被用作左值时,使用的是对象的身份(在内存中的位置)。
常见的运算符中用到左值的场景包括:
- 赋值运算符。左侧和右侧的数值均为左值。
- 取地址符(&),作用于一个左值对象,返回一个指向该左值对象的指针,这个指针是一个右值。
- 内置解引用运算符、下标运算符、迭代器解引用运算符等等。
- 内置类型和迭代器的递增递减运算符。
优先级和结合律
略
求值顺序
略
算术运算符
略
逻辑和关系运算符
略
成员访问运算符
点运算符和箭头运算符都可用于访问成员。点运算符用于获取类对象中的一个成员,箭头运算符与点运算符有关,即ptr->mem==(*ptr).mem。
箭头运算符作用于一个指针类型的运算对象(确切地说,是一个指向类对象的指针),其结果是一个左值。
条件运算符
这个内容在C中是学习过的,在这里进行简单地复习。
所谓的条件运算符,就是将if else语句简化到单个表达式之中,格式如下:
cond? expr1:expr2;
其中cond是执行的条件判断,当其为真时,执行表达式1(expr1),否则执行表达式2(expr2).
int grade=95;
string final_grade=(grade>=60)?"pass":"gua_ke";
嵌套的条件预算符
正如if_else if语句一样,C++支持条件运算符嵌套使用。
final_grade=(grade<60)? "gua_ke"
:(grade>=90) ? "high"
:pass;
位运算符
这个在C语言单片机等中使用的较多,C++中使用并不频繁。略。
sizeof运算符
sizeof运算符首先是一个运算符!不是一个函数!它可以返回一条表达式或者一个类型名字所占的字节数。
int data,*p;
sizeof (int); //返回一个整形数据所占的字节数;
sizeof data; //与上面一样
sizeof p; //返回一个指向整形数据的指针所占的空间大小
sizeof *p; //返回指针所指空间类型的内存大小
逗号运算符
表达式的结果是最右边的表达式的结果
类型转换
包括隐式类型转换和显式类型转换。
显式转换也称强制转换(cast)。
static_cast
任何具有明确定义的类型转换,只要不包括底层const,都可以使用static_cast.
int i,j;
double slope= static_cast<double>(i)/j;
const_cast
const_cast只能改变运算对象的底层const。
const char *p;
char *p=const_char<char>(p);
//可以用这种强制转换来去掉常变量的恒定属性。
reinterpret_cast
reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。
这个不介绍了,因为易出错。且不同机器的支持程度不同。
运算符优先级表
见147页。
语句
简单的部分、之前学过的部分全部省略。这里只介绍try语句块和异常处理部分。
异常指程序运行时的反常行为。典型的异常包括失去数据库连接和遇到意外输入等部分。当程序检测到一个无法处理的问题时,就需要用到异常处理。c++中的异常处理主要有两个方面:
- throw表达式
- try语句块。try语句块以关键字try开始,以一个或多个catch结束。try语句块中代码抛出的异常将会被魔域一个catch子句处理。这个整体被称为异常处理代码。
- 一套异常类。
throw表达式
在throw后面加一句表达式,来进行异常抛出。
try语句块
try语句块的通用语法形式为:
try{
program-statements(正常在运行的代码)
} catch (exception-declaration这个是异常声明){
handler-statement进行的进一步处理
}......
函数
形参和实参
和C中一样,实参是形参的初始值,实参的类型必须和形参对应。函数的形参列表可以为空,但是不能省略。
局部对象
C++中,名字有作用域,对象有生命周期。
局部变量:包括形参和位于函数体内的变量。
自动对象
只存在于块执行期间的对象。例如形参。
局部静态对象
对于需要将局部变量的生命周期贯穿到函数调用及之后的时间的变量,我们需要使用static类型来定义这些局部变量。
局部静态对象在程序执行第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间,即使对象所在的函数结束也不会对他有影响。
\一个统计自己被调用了多少次的函数
size_t count_calls()
{
static size_t ctr = 0;
return ++ctr;
}
函数声明
函数的名字必须在使用之前声明。函数只能定义一次但是可以声明多次。函数的声明只需要描述函数的接口。
在头文件中进行函数声明
C++建议将变量、函数均在头文件进行声明,在源文件进行定义。
含有函数声明的头文件应该被包含到定义函数的源文件中。且需要注意引入的时候是双引号而不是尖括号。
分离式编译
即将不同的功能分散在不同的文件里,然后连接在一起进行编译。
参数传递
如果形参式引用类型,那么该形参将会绑定到对应的实参上;否则,将实参的值拷贝给形参。 前者被称为引用传递,后者被称为值传递。这也就是说,C++中的形参未必是对象,也有可能是引用。
传值函数
当进行值传递时,由于进行了拷贝操作,所以实参的数值不会被改变。
指针形参
指针形参在调用函数时可以改变被调用的实参对象的数值。C++中建议使用引用来代替指针。
传引用参数
正如所叙,在函数调用引用形参时会改变对应的实参的数值,在这里其作用是和指针相同的。
对于在函数内部功能上不需要对引用形参的数值进行改变的,一般用常量引用(const)来保证安全。
//比如比较两个string的长度
bool isShorter(const string &str1, const string &str2)
{
return str1.size()<str2.size();
}
使用形参返回多个值
一般,函数只能返回一个值。可以使用引用形参来达到返回多个值的目的。
const形参和实参
因较易理解而略之
数组形参
数组有两个特殊性质:
- 无法利用一个数组对一个数组进行赋值;
- 使用数组时通常把数组转化为指针。
基于以上两个特质,在函数中调用一个数组时,传递的其实是指向数组首元素的指针。
由于数组是以指针的方式传递给函数的,因而一开始函数并不知道数组的确切尺寸,因而函数在一开始并不知晓数组的长度,因此需要引入一些额外的信息进行补充,主要有:
使用标记指定数组长度
比如C语言中的字符串数组。
使用标准库规范
即在C++中传递指向数组的首指针和尾后指针。
比如说:
void print(const int *begin_p,const int *end_p)
{
while (begin!=end_p)
cout<<*begin++<<endl;
}
显式传递一个表示数组大小的形参
这个不用多介绍了,很直接。
数组引用形参
形参可以作为数组的引用。
void print( int (&arr)[10])
{
for (auto elem:arr)
cout<<elem<<endl;
}
main()函数处理命令行选项
int main(int argc, char *argv[]){...}
上述主函数中的第二个参数是一个数组,数组里的每个元素都是一个字符串数组的首指针;第一个参数是这个数组中元素的个数。因而也可写为:
int main(int argc, char **argv){...}
含有可变形参的函数
有的时候我们无法提前预知应该向函数传递几个函数,因而希望能够处理不同数量实参的函数,C++11提供了两种主要的方法:
- 如果所有的实参类型相同的话,可以传递一个名为initializer_list的标准库类型;
- 如果实参的类型不同,可以使用可变参数模板
C++还有一种特殊的形参类型(省略符,可以用它来传递可变数量的实参)。
initializer_list形参
使用前提是所有的函数输入参数类型相同,数量未知。
用法:
/*使用方式示例
2. initializer_list<Type> name;
2. initializer_list<Type> name{a,b,c,...};
3. 。。。。。
*/
void error_msg(initializer_list<string>ls)
{
for (auto beg=ls.begin();beg!=ls.end();beg++)
cout<<*beg<<" ";
cout<<endl;
}
可以看出,initializer_list与vector相似,都是模板类型,也都可以使用.begin()和.end(),但是initializer_list在初始化时其元素的个数就是固定的了。
省略符形参
省略符(...)形参应该仅仅用于C和C++通用的类型。大多数类类型的对象在传递给省略符形参时都无法正确拷贝。
返回类型和return语句
return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。return语句有两种形式:
- return;
- return expression;
对于无返回值的函数,可以在函数体中不写return,但是若想在函数运行的中间退出该函数,则可以使用return的第一种形式。
对于有返回值语句只能使用第二种形式。
如果return后面是引用,则返回引用的引用的对象。
引用返回左值。其他函数类型返回右值。
返回数组指针
由于数组不能被拷贝,所以函数不能返回数组。但是函数可以返回数组的指针或引用。主要包括以下四种方法:
//方法一:
typedef int arrT[10]; // arrT是一个类型别名,他表示含有10个整形数据的数组
using arrT=int[10]; //含义与上一条指令相同
arrT* func(int i); //用该类型来定义函数的返回值。
//方法二:
int (*func(int i))[10];
//方法三:使用尾置返回类型(trailing return type)
auto func(int i)->int(*)[10]; //func函数返回整数型的指针,一共有10个。
//方法四: 使用decltype
//若我们直到函数返回的指针指向哪一个数组,就可以使用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;
}
函数重载
如果同一作用域的几个函数名字相同但是形参列表不相同,我们则称之为重载(overloaded)函数。
下面记录自己不太熟练的地方:
特殊用途语言特性
默认实参
即再不输入该值时默认得到的参数,这个特性和python中的特性是一致的。
typedef string::size_type sz;
string screen(sz ht=24, sz wid=80, char backgrnd=' ');
string windows;
windows=screen();
windows=screen(66,256);
但是C++只能允许忽略尾部的形参,因此下面的语句是不合法的。
windows=screen(,,'?'); //错误的,只能忽略末尾的形参。
内联函数和constexpr函数
内联函数
在函数类型前面添加一个inline,即可将该函数声明为内联函数。
//示例:一个比较两个字符串并返回较小的字符串的函数
inline const string & ShorterString(const string &s1, const string &s2)
{
return s1.size()<=s2.size() ?s1:s2;
}
constexpr函数
constexpr函数是指能够用于常量表达式的函数。该函数要求函数的形参和返回值都必须是常量。
一般内联函数和constexpr函数都在头文件中进行声明和定义。
assert预处理宏(preprocesser marco)
所谓预处理宏是一个预处理变量。assert用一个表达式作为其条件:
assert(expr);
首先对expr求值,若为假,则输出信息终止程序执行;若为真,则assert什么也不做。
NDEBUG预处理变量
定义NDEBUG可以避免检查各种条件所需的运行时开销,当然此时根本就不会执行运行时检查。
如果NDEBUG未定义,将执行#ifndef和#endif之间的检查,如果NDEBUG被定义,这些代码将会被忽略掉。
函数匹配
略。
函数指针
函数指针指向的是函数而非对象。与其他指针一样,函数指针指向某种特定类型。函数的类型由他的返回类型和形参类型共同决定,而与函数名无关。
//示例代码:对于一个比较两个字符串长度的函数,如何定义函数指针?
//函数的声明方式
bool lengthcompare(const string &,const string &);
//函数指针的声明方式
bool (*pf)(const string &,const string &);
//对于一个已经声明过的函数,可以直接使用函数指针调用该函数,由三种调用方式:
bool b1=pf("liangzi","zuishuai");
bool b2=(*pf)("liangzi","zuishuai");
bool b3=lengthcompare("laingzi","zuishuai");
重载函数的指针
一个函数指针只能指向一个函数,所以对于重载函数即使名字相同,但是指针只会指向返回类型和形参类型精确匹配的那个函数。
函数指针形参
即 将函数指针作为形参,此时可以直接使用函数名,编译器会自动将其视为函数指针。
void useBigger(const string &s1,const string &s2,bool pf(const string &,const string &));
//等价声明:
void useBigger(const string &s1,const string &s2,bool (*pf)(const string &,const string &));
返回指向函数的指针
略。
将auto和decltype用于函数指针类型
如若我们明确知道返回的函数是哪一个,那么我们就能使用decltype简化书写函数指针返回类型的过程。只是需要几句decltype作用于某个函数时,他返回的是函数类型而非指针类型。