P124
- 在表达式求值之前,小整数类型的运算对象被提升成较大的整数类型,所有运算对象最终会被转换成同一类型。
- 一元正号运算符可以作用于指针,不能作用于迭代器,比如:
int a=1;
int*p=&a;
cout<<(+p)<<endl;//相当于取p的值
P125
条件表达式:
当条件运算符的两个表达式都是左值且能转换成同一种左值类型时,运算的结果是左值;否则是右值。
P161
switch:
switch后面的表达式必须有整数类型或枚举类型;case后面的值必须是整形常量表达式;
比如:
string s;
switch(s){}//错误,s不能用作switch的表达式
int a=1;
switch(a)
{
case a:break;//错误,不是整形常量表达式
case 2.1:break;//错误,不是整形常量表达式
}
case语句中可以定义变量,如果另一个case中使用了上一个case中定义的变量,则可能会出现问题,由于可能会跳过某些case,则当跳过变量的定义时,而转到使用变量的case 语句中,由于该变量已经定义,如果没有初始化,则可以使用,如果有初始化,则这种跳转是错误的;类似的还有goto语句。比如:
switch(2)
{
case 1:
int a=1;//错误:控制流可能绕过该变量的显式初始化
int b; //正确:b没有被初始化
string s;//错误:控制流可能绕过该变量的隐式初始化
break;
case 2:
a=1;//错误,a可能没有被初始化
b=1;//正确,b没有初始化
s.empty();//错误,s可能没有被初始化
}
goto a;
int a=1;//跳过了a的初始化,错误!ps:goto的标签标识符可以和程序中的其它实体的标识符使用同一个名字而互不干扰
a:
cout<<a;
goto语句:无条件跳转到同一函数内的另一条语句
跳过变量的初始化而使用该变量显然是行不通的,因此C++语言规定:不允许跨过变量的初始化语句直接跳转到该变量作用域内的另一个位置。因此,如果case中有初始化的变量定义,如果该变量没有被定义在{}字块中,则不管后面有无case语句或default语句,该switch语句都是不合法的!
P 169-170
do while语句:不能在while中定义变量,如果可以定义变量,那么如果do中如果不使用该变量的话,要么一次不执行,要么永久执行,没有意义;如果使用该变量,那么do先执行,则变量的使用出现在变量的定义之前,这不合理!
P197
main:处理命令行选项
main 的形参:main(int argc,char**argv);argc指定一共有多少个字符串。argv是指向字符串指针的指针,即指向一个字符串指针数组,数组的最后一个元素之后的元素值是0.即 argv[argc]==0;
P206
使用尾置返回类型
int f1();//正常的写法
auto f1()->int;//尾置写法
P207
main函数不能被重载
P208 重载和const形参
void f1(int);
//重复声明了void f1(int);顶层const不影响传入的参数,底层const 影响
void f1(const int);
void f1(int&);
void f1(const int&);//底层const,是函数重载
P210 重载与作用域
名字查找发生在类型检查之前,如果在当前作用域中找到对应的名字,则停止查找(即忽略掉外层作用域中的同名实体)
P212
函数的某个默认实参只能被定义一次,或者在某次声明中,或者在函数定义中;
比如:
int f1(int a,int b=1);
int f1(int a=1,int b=1);错误,默认参数只能被定义一次
int f1(int a=1,int b=2){}//错误,默认参数只能被定义一次
默认实参初始值:
用作默认实参的标识符在函数声明所在的作用域内解析,找不到则在外层作用内找,默认实参的求值过程发生在函数调用时;局部变量不能用作默认实参;
int a=1;
int d=2;
void f1(int b=a,int c=d);
void main()
{
d=200;
int a=3;
f1();//f1(1,200),a屏蔽了外层的全局变量a,但是函数调用时用的是全局变量,所以是1,另外d也是全局变量且调用时值是200,所以调用形式是f1(1,200)
}
P214
inline函数 constexpr 函数,比如
inline int f1(){}
constexpr int f1(){return 0;}
constexpr 函数的返回值及所有形参的类型都得是字面值类型,而且函数中只能有且仅有一条return语句,也可以包含其它语句,只要这些语句在运行时不执行任何操作就行。在类中定义的成员函数及constexpr函数默认是inline 函数。
和其它函数不一样,inline 函数和constexpr函数可以定义多次,但是必须都一致。一般定义在头文件中。
P220
二义性调用:
void manip(long);
void manip(float);
manip(3.14);//错误:二义性调用
所有算数类型转换的级别都一样。double可以转换成float或者long。所以该调用具有二义性。
void f1(int);
void f1(short);
f1(‘a’);//首先类型提升为int,所以调用f1(int)
P243
类型成员也有访问权限
class c
{
public:
using sz=std::string::size_type;//sz是一个public类型成员,必须先定义后使用
};
P245
可变数据成员
class c
{
void f1()const
{
++m;
}
mutable int m;//m可变, const 成员函数也可以改变它
};
P252
友元声明和作用域
友元声明的作用是影响访问权限,它本身并不是声明或定义,即使在类的内部定义了友元函数,也必须先在外部声明该友元函数才能使用它。
class c
{
friend void f1(){}
void f2(){f1();}//错误,f1没有被声明,无法使用
}
P 255
C++规定:内层作用域可以重新定义外层作用域中的名字,即使该名字已经在内层作用域中使用过。然而在类中,如果成员使用了外层作用域中的某个名字且该名字代表一个类型,则类不能在之后重新定义该名字:
typedef int a;
class
{
a f1(){};
typedef int a;//错误,由于f1的返回值是a,因此不能重新定义a
};
尽管C++规定这是错误的代码,但是很多编译器仍将通过这样的代码。
因此一般类型成员出现在类定义的开始处。
P259
成员的初始化顺序与它们在类定义中的出现顺序一致。
class x
{
int i,j;
x(int val):j(val),i(j){}//先用未初始化的j初始化i,然后用val初始化j,因此错误
};
P265
explicit用于说明不能隐式转换。它只对有一个实参的构造函数有效。且不能出现在类外部定义的构造函数处。
P266
聚合类(aggregate class),当一个类满足如下条件,其是聚合类。
:所有成员都是public
:没有定义任何构造函数
:没有类内初始值
:没有基类,没有virtual函数
//aggregate class
class c
{
public:
int a;
int b;
};
c a={1};//c.a=1,c.b=0。与初始化数组元素的规则一样
//not an aggregate class
class c
{
public:
int a;
int b;
c1(int val):a(val){}
};
c a={1};//c.a=1,c.b是一个随机值
P268
构造函数不能是const,但是可以是constexpr,成员函数如果是constexpr,则必须符合constexpr函数的所有要求。成员函数是constexpr不代表其是const!
constexpr:如果变量是constexpr,则其值在编译器就可以确定;如果函数(包括成员函数)是constexpr,则其返回值可以是常量表达式,也可以不是;
P271
静态成员可以用作函数参数的默认值;
静态数据成员的类型可以是它所属类的类型。非静态数据成员不能是它所属类类型,只能是类的引用或指针。