1.函数调用也是是一种特殊的运算符,他对运算对象的数量没用限制。
2.C语言中便于记忆有“左值可以位于赋值语句的左侧,右值不能”的说法。在C++语言中,一个左值表达式的求值结果是一个对象或者一个函数,然而以常量对象为代表的某些左值实际上不能作为赋值语句的左侧运算对象。此外,虽然某些表达式的求值结果是对象,但他们是右值而不是左值。可以做一个简单的归纳:当一个对象被用作右值的时候,用的都是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。
3.关于左值和右值的一个重要的原则是:在需要用到右值的地方可以用左值来代替,但是不能把右值当成左值(也就是位置)来使用。当一个左值被当成右值来使用时,实际使用的是他的内容(值)。
4.输出运算符(<<)没有明确规定何时以及如何对运算对象求值,因此下面输出的表达式是未定义的:
int i=0;
cout<<i<<" "<< ++i<<endl; //输出1 1
int j=0;
cout<<j<<" "<< j++ <<endl; //输出1 0
5.算术运算符的运算对象和求值结果都是右值。
6.C++11新标准中规定整除运算中,商一律向0取整(即直接切除小数部分)。
7.根据取余运算的定义,如果m和n是整数且n非0,则表达式(m/n)*n+m%n的求值结果与m相等。隐含的意思是,如果m%n!=0,则他的符号和m相同。除了-m导致溢出的情况,其他时候(-m)/n和m/(-n)都等于-(m/n),m%(-n)等于m%n,(-m)%n等于-m%n。
8.逻辑与运算符(&&)和逻辑或运算符(||)都是先求左侧运算符的值再求右侧运算符的值,当且仅当左侧运算对象无法确定表达式的求值结果时才会计算右侧运算对象的值。这中策略称之为短路求值。
9.赋值运算满足右结合律:
int i,j;
i=j=0; //正确:都被赋值为0
注意运算符的优先级较低:
int i;
while( (i=get_value()) !=42){ /**/ }
10.递增和递减运算符各有两个版本:前置版本和后置版本:
int i=0,j;
j=++i; //j=1,i=1:前置版本得到递增之后的值
j=i++; //j=1,i=2:后置版本得到递增之前的值
建议:除非必须,否则不用后置版本的运算符:前置版本的递增运算符避免了不必要的工作,它把值加1后直接返回改变了的运算对象,与之相比,后置版本需要将原始值存储下来以便于返回这个未修改的内容。如果我们不需要修改前的值,那么后置版本的操作就是一种浪费。
11.
vector <int> v={1,2,3};
auto i=v.begin();
while(i!=v.end())
cout<<*i++<<" "; //输出当前的值并将i向前移动一个元素
//程序将输出1 2 3
后置递增运算符的优先级高于解引运算符。也就是说,*i++等价于*(i++)。i++把i的值增加1,然后返回i的初始值的副本作为其求值结果,此时解引用运算符的运算对象是i未增加之前的值。最终,这条语句输出i开始时指向的那个元素,并将指针向前移动一个元素。
int a[]={1,2,3};
int *p=a,*q=a;
cout<< *p++ <<endl; //输出1 等价于*(p++)
cout<< *++q <<endl; //输出2 等价于*(++q)
12.成员访问运算符:点运算符(.)和箭头运算符(->)都可以访问成员,其中,点运算符获取类对象的一个成员;箭头运算符与点运算符有关,表达式ptr->men等价于(*ptr).mem。
string s1="a string",*p=&s1;
auto n=s1.size(); //运行string对象s1的size()成员
n=(*p).size(); //运行p所指对象的size()成员
n=p->size(); //等价于(*p).size()
因为解引运算符的优先级低于点运算符,所以执行解引用运算的子表达式两端必须加上括号。
13.位运算符的运算对象可以是带符号的,也可是无符号的(但只能是整数(非运算符可以作用于其他类型)).如果运算对象为带符号的且它的值为负,那么位运算符如何处理对象的“符号位”依赖于机器。而且,此时左移操作可能会改变符号位的值,因此是一种未定义的行为。
14.左移运算符(<<)在右侧插入值为0的二进制位;右移运算符(>>)的行为依赖于其左侧运算对象的类型:如果该运算对象是无符号类型,往左侧插入值为0的二进制位;如果该运算对象是带符号类型,往左侧插入符号位的副本或值为0的二进制位。
15.移位运算符(又叫IO运算符)满足左结合律。优先级:比算术运算符的优先级低,但比关系运算符、赋值运算符和条件运算符的优先级高:
cout<<42+10; //正确:+的优先级高
cout<<(10<42); //正确
cout<< 10<42; //错误:试图比较cout和42!
16.sizeof运算符返回一条表达式或一个类型名字所占的字节数。满足右结合律,其所得的值是一个size_t类型的常量表达式。sizeof返回的是表达式结果类型的大小,并不实际计算运算对象的值:所以,在sizeof的运算对象中解引用一个无效指针仍然是一种安全的行为,因为指针实际上并没有被真正使用。
17.sizeof的运算结果部分的依赖于其作用的类型:
-对char或者类型为char的表达式执行sizeof运算,结果得1。
-对引用类型执行sizeof运算得到被引用对象所占空间的大小。
-对指针执行sizeof运算符得到指针本身所占空间的大小。
-对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小,指针不需要有效。
-对string对象或vector对象执行sizeof运算只能返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。
#include <iostream>
#include <string>
#include <iterator>
#include <vector>
using namespace std;
int main()
{
typedef struct node
{
int a,b;
}n_int;
n_int a;
n_int &r=a,*p=&a;
cout<< sizeof a <<endl; //8
cout<< sizeof r <<endl; //8
cout<< sizeof &r <<endl; //4
cout<< sizeof p <<endl; //4
cout<< sizeof *p <<endl; //8
string s,s1="lhs myl",s2[10];
cout<< sizeof s <<endl; //4
cout<< sizeof s1 <<endl; //4
cout<< sizeof s2 <<endl; //40
vector <int> v1;
cout<< sizeof v1 <<endl; //12
vector <int> v2={1,2,3};
cout<< sizeof v2 <<endl; //12
vector <string> v3;
cout<< sizeof v3 <<endl; //12
vector <n_int> v4;
cout<< sizeof v4 <<endl; //12
return 0;
}
typedef struct node
{
}n_int;
n_int a;
cout<< sizeof a <<endl; //结果为1
//因为要为结构体分配内存空间,所以尽管结构体为空,sizeof不为0
18.逗号运算符含有两个运算对象,按照从左到右的顺序依次求值(其规定了运算对象的求值顺序)。对于逗号运算符来说,首先对左侧的表达式求值,然后将求值结果丢弃掉。逗号运算符得到的真正结果是右侧表达式的值。如果右侧运算对象是左值,那么最终的求值结果也是左值。
19.在大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针。允许将指向非常量的指针转换成相应的常量类型指针,对于引用也是这样。
20.显式转换:cast-name <type> (expression).其中,type是转换的目标类型而expression是要转换的值。如果type是引用类型,则结果是左值。cast-name指定了执行的哪种转换,是static_cast,dynamic_cast,const_cast和reinterpret_cast中的一种。
21.static_cast:任何具有明确定义的类型,只要不包含底层const,都可以使用static_cast。
int i=5,j=8;
double ans=static_cast<double>(j)/i;
cout<<ans<<endl;
static——cast对于编译器无法自动执行的类型转换非常有用。例如,我们可以使用static_cast找回存在于void*指针中的值:
double ans=1.6;
void *p=&ans;
cout<< *p <<endl; //错误
double *dp=static_cast<double*>(p);
cout<< *dp <<endl;
22.const_cast只能改变运算对象的底层const。
const int *p;
int *p1=p; //错误:常量指针不能赋值给非常量指针
int *p2=const_cast<int*>(p); //正确:但是通过p2写值是未定义的