运算符
赋值运算符
int ival, jval ;
ival = jval = 0; //先赋值jval,然后赋值ival
递增递减运算符
递增运算符(++)和递减预算符(–)为对象加1或减1操作提供了一种简洁的书写方式
int i=0, j ;
j = ++i; //前置版本得到递增之后的值,j=1, i=1
j = i ++; //后置版本得到递增之前的值,j=1, i=2
注意代码的简介的书写方式:
cout << *iter++ <<endl;
//等价于下面的形式
cout << *iter << endl;
++iter ;
上面形式更加简洁。
条件运算符
条件运算符(?:)为一种简洁的if-else逻辑嵌入到单个表达式中。
string finalgrade = (grade < 60) ? "fail" : "pass" ;
嵌套条件运算符
finalgrade = (grade > 90) "high pass" : (grade < 60) ? "fail" : "pass" ;
算术转换
整型提升,负责将小整数类型转换成较大的整数类型。
3.141L + 'a' ; //将'a'提升为int,然后提升为long double
double dval ;
int ival ;
dval + ival ; // ival转换成double
隐式类型转换
数组转换为指针:
int ia[10]; //
int *p = ia; //ia转换为指向数组首元素的指针
指针的转换
- 常量数值0或者字面值nullptr能转换为任意的指针类型;
- 指向任意非常量的指针可以转换为void * ;
- 指向任意对象的指针可以转换为const void*;
int i;
const int &j = i ; //非常亮转换为常量引用
const int *p = &i ; //非常量的地址转化为const地址
int &r = j, *q = p; // 错误,const不能转化为常量
显示转换
- static_cas: 任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。
double slop = static_cast<double>(j) / i ; //进行强制类型转换,以便进行浮点除法
const_cast,只能改变底层const。底层cosnt代表指针所指的对象是一个常量,顶层const代表指针本身是个常量。
const char *pc ; //
char *p = const_cast<char*>(pc) ; //正确,但是通过p写值式未定义的。
语句
switch中,要注意不要漏泄break,这样容易引发缺陷。
范围for语句,器语法形式是:
for (declaration: expression)
statement
//example
vector<int> v = { 1,2,3,4 };
for (auto &r : v)
{
r *= 2;
}
cout << v[1]<< endl;
for (auto beg = v.begin(); beg != v.end(); beg++)
{
auto &r = *beg; //引用
r *= 2;
}
cout << v[1] << endl;
try语句块与异常
定义try语句块的语法形式是
try{
program-statement;
}
catch(exception-declaration){
}
catch(exception-declaration){
}
函数
形参和实参:实参是形参的初始值。
局部对象
在c++语言中,名字有作用域,对象有生命周期。
- 名字的作用域是程序文本的一部分,名字在其中可见;
- 对象的生命周期是程序执行过程中该对象存在的一段时间。
局部静态对象:零局部变量的生命周期贯穿函数调用以及之后的时间。这可以通过将局部变量定义为static类型,局部静态对象在程序执行路径第一次经过对象定义语句时初始化,并知道程序终止被销毁。
int count_calls(){
static int ctn = 0; //调用结束后这个值仍然有效
return ++ctn ;
}
int main(){
for(int i=0; i !=10; i++){
cout << count<calls() << endl ;
}
}
如果局部静态变量没有显示的初始值,那么它将执行值初始化,内置类型的局部静态变量初始化为0。
函数声明
函数声明和变量声明一样,可以声明多次,但是只能定义依次。函数声明也称为函数原型。函数的三要素:返回类型、函数名、形参类型。
分离式编译:将程序分割到几个文件中去。
参数传递:每次调用函数时会重新创建它的形参,并用传入的实参对器进行初始化。
当形参是引用类型时,其对应的实参引用传递,引用形参是它绑定的兑现的别名,即它对应的实参的别名。
传值参数:当初始化一个非引类型的变量时,初始值被拷贝给变量。函数对形参的所有操作都不会影响实参。
指针形参:执行指针拷贝时,拷贝的是指针的值,那么两个指针为不同的指针,我们可以通过指针访问其对象。
void reset(int *p){
*ip = 0 ; //改变指针所指对象的值
ip = 0; //只改变了ip的局部拷贝,实参没有改变。
}
传递引用参数。通过引用形参,允许函数改变一个或多个实参的值
void reset(int &i){
i = 0; //改变了i所指对象的值
}
使用引用避免了拷贝。拷贝大的类型的对象或者容器对象比较低效,有的类类型不支持拷贝操作,那么只能通过形参访问该类型的对象。
使用形参返回额外信息可以将返回值作为传参的引用类型。
一个函数只能返回一个值,然而有时候需要同时返回多个值,我们可以定义一个新的数据类型来包含这些需要返回的值,当然,引用形参为我们一次返回多个结果提供了另一种有效的途径。看下面的这个例子,我们给函数多传入了一个引用实参。
string::size_type find_char(const sting &s, char c, string::size_type &occurs}{
auto res = s.size();
occurs = 0;
for(decltype(ret) i=0; i!=s.size(); ++i){
if(s[i] == c){
if(ret == s.size()) ret = i; //返回第一次出现的位置
occur ++; //返回总共出现的次数}
}
return ret;
}
const形参和实参
当形参是const类型时,要主要关于顶层const和底层const的区别。
所谓顶层const,就是指向的变量是一个常量;底层const,是一个常量地址。看例子:
const int ci = 42; //顶层,ci无法改变
int i = ci; //正确,拷贝ci是可以的,此时忽略其顶层const
int * const p = &i; //顶层的const
*p = 0; //正确,通过p改变对象的内容
指针或引用形参的const
int i = 42;
const int *cp = &i; //正确,但是cp不能改变i
const int &r = i; //正确但是r不能改变r
const int &r2 = 42; //正确
int *p = cp; //错误,类型不匹配;
int &r3 = r; //错,类型不匹配
int &r4 = 42; //错,不能用字面值初始化一个非常量的引用
可见规则为:
- 可以使用非常量的对象初始化一个常量对象,但是常量对象不能初始化非常量对象。
- 一个普通类型的引用必须用同类型的对象进行初始化。
将上面的初始化规则应用到参数传递中,见:
int i = 0;
const int ci = i;
const int ci = i;
string::size_type ctr = 0;
reset(&i) ; //调用的是int*
reset(&ci); //错误:不能用指向const int的指针初始化int *;
rest(i) ; //调用int &
reset(ci) ; //不能讲普通引用绑定到const 对象上
reset(42); //错误,普通引用不能绑定到字面值(常量)
注意尽量使用常量引用
当函数的形参不会改变时,定义为普通的调用时一种常见的错误,这样会使得函数嫩egg修改它的实参的值。
此外,使用引用而非常量引用也会极大的限制函数所能接收的实参类型。
函数的返回
函数的值时如何被返回的?
返回一个值的方式和初始化一个变量或者形参的方式完全一样,返回的值用于用于初始化调用点的一个临时量。
函数返回一个引用
const string &shortString(const string &s1, const string &s2){
return s1.size()<s2.size() ? s1:s2 ;
}
注
- 不要返回局部变量的引用或者指针
返回数组指针。数组不能被拷贝,所以函数不能返回数组。不过,函数可以返回数组的指针或者引用。
using arrT = = int[10];
arrT* func(int i); //func返回一个指向含有10个整数数组的指针
\\要想在声明func时不适用类型别名,我们要牢记被定义的名字后面数组的维度
int arr[3] = {1,3,5 };
int (*p)[3] = &arr;
//这句其实也等价于
int *P = arr ; //数组名会由编译器换为数组首指针
如果我们想定义一个返回数组指针的函数,则数组的维度必须跟在has名字之后。其形式如下
\\ 形式Type (*function(par_list)[dimension)
int (*func(int i))[10]; //这是一个例子
const_cast和重载
在函数的重载中,一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。也就是顶层const在参数传递时会被忽视掉(有点抽象这)。
首先回忆一下const_cast
const_cast只能改变运算对象的底层const。
这里我就要bb几句这个const了:
- 顶层英文为top-level,底层为low-level,字面意思很难理解,反而从英文的角度有一种优先级的感觉,就是说,top-level级别很高,不能动;low-level的是可以改变值的。
- 引用一句书上的“顶层const表示指针本身是一个常量,底层const表示指针所指的对象是一个常量。
- More general,** 一个顶层const可以表示任意的对象是常量**。底层const则与指针和引用这些符合类型部分有关。也就是指针类型可以是顶层也可以是底层”。
- 一个特殊情况,用于声明引用的const都是底层const。
那么const_cast的作用是什么呢?
其实就是将常量对象转化为非常量对象,即cast away the const-去掉const性质。当去掉了const性质,我们就可以对改对象进行写操作了。不过我想说,底层const不是本来就可以进行值的更改吗?
说了这么多,言归正传,这个功能如何应用到函数重载中呢?看例子:
string &shortString(string &s1, string &s2){
auto &r = shortString(const_cast<const string&>(s1),const_cast<const string&>(s2));
return const_cast<string&>r ;
}
这个例子就是闲的没事了,首先它将其实参强制转换为const的引用,然后调用了shortString的const版本,const版本返回const string的引用,然后将其转换为一个普通的string&。问题:const_cast也可以将非const转化为const?
constexpr用于常量表达式的函数
** 调试助手assert**
函数匹配
当有几个重载函数时,可能需要进行函数匹配来确定应该使用哪个重载函数。