=======================================================
4.1 什么是表达式
=======================================================
表达式由一个或多个 操作数(aperand) 、以及应用在这些操作数上的操作构成。
应用在操作数上的操作由 操作符(operator) 表示。分:一元操作符和二元操作符。
=======================================================
4.2 算术操作符
=======================================================
操作符 功能 用法 说明
* 乘 expr * expr
/ 除 expr / expr
% 求余(取模) expr % expr 只能用于整数,若有一个或两个数为负,余数的是否为负则取决于机器
+ 加 expr + expr
- 减 expr - expr
标准C++头文件limits提供了与内置类型表示有关的信息,如一个类型能表示的{zd0}值和最小值。
标准C头文件climits和cfloat定义了提供类似信息的预处理器宏。
=======================================================
4.3 等于、关系和逻辑操作符
=======================================================
操作符 功能 用法
! 逻辑非 !expr
< 小于 expr < expr
<= 小于等于 expr <= expr
> 大于 expr > expr
>= 大于等于 expr >= expr
== 等于 expr == expr
!= 不等于 expr != expr
&& 逻辑与 expr && expr
|| 逻辑或 expr || expr
注:这些操作符的结果是bool类型(true或false)。
int elem_cnt = 0;
vector<int>::iterator iter = ivec.begin();
while( iter != ivec.end() )
{
elem_cnt += *iter < some_value;
++iter;
}
二元操作符存在潜在的缺点:左右操作数的计算顺序是未定义的,因此计算过程必须是与顺序无关的:
if( ia[index++] < ia[index] ) ... //目的是ia[0]与ia[1],但实际上很可能是ia[0]与它自己
所以应该写成:
if( ia[index] < ia[index+1] ) ...
=======================================================
4.4 赋值操作符
=======================================================
int ival = 1024;//初始化操作,只可以被初始化一次
ival = 2048; //赋值操作,可以被重复多次
当我们把不同类型的表达式赋值给一个对象时,编译器会试着隐式将右操作数转换成被赋值对象的类型,如果可能的话。若不能则编译出错。
ival= 3.1415926; //3
int *pi;
pi = ival; //错误
赋值表达式可以被当作一个子表达式:
while( (ch=next_char()) != '\n' ) ... //外加的小括号是必需的
赋值操作符也可以被连接在一起,只要操作数都是相同的类型:
int ival, jval, *pval;
ival = jval = 0; //ok
ival = pval = 0; //错误,即使单独赋值都是正确的
int ival = jval = 0; // 可能合法,也可能不合法,因为jval必须实现定义成适当的类型
应该写成:
int ival=0, jval=0;
复合赋值操作符:
+= -= *= /= %=
<<= >>= &= ^= |=
=======================================================
4.5 递增和递减操作符
=======================================================
递增(++)、递减(--)
为什么C++不叫++C?
转自:
C++的C说明了它本质上是从C语言演化过来的,C++语言是C语言的超集,是在C语言基础上进行的扩展(引入了new、delete等C语言中没有的操作符,增加了面向对象程序设计的支持,等等),是先有C语言,在进行C++。根据自增操作符前、后置形式的差别,C++表示对C语言进行扩展之后,还可以使用C语言的内容,而写成++C则表示无法再使用C的原始值了,也就是说C++不能向下兼容C了,这与实际情况不符。
=======================================================
4.6 复数操作
=======================================================
C++不但支持一般的算术操作符,如加、减、乘、除,而且还支持复数类型与内置类型的混合运算。如:
#include <complex>
complex<double> a;
complex<double> b;
//...
complex<double> c = a*b + a/b;
complex<double> complex_obj = a + 3.14159;
或:
double dval = 3.14159;
complex_obj = dval;
但是反过来则不行:
dval = complex_obj; //错误
可以显示的知名我们要用复数对象的哪部分来赋值:
double re = complex_obj.real();
double im = complex_obj.imag();
或
double re = real(complex_obj);
double im = imag(complex_obj);
支持四种复合赋值:+=、-=、*=、/=
复数的输出:
complex<double> complex0(3.14159, -2.171);
cout << complex0 << endl;
输出:
(3.14159, -2.171)
复数的输入,下面任意格式都可以:
cin >> a >> b >> c;
3.14159 (3.14159) (3.14, -1.0)
复数类支持的其他操作包括:sqrt()、abs()、polar()、sin()、cos()、tan()、exp()、log()、log10(),以及pow()。
=======================================================
4.7 条件操作符
=======================================================
expr1 ? expr2 : expr3;
使用:
int i=10, j=20, k=30;
cout << "the larger value of " << i << " and " << j << " is " << (i>j ? i:j) << endl;
条件操作符可以被嵌套,但是深度的嵌套比较难读:
//max被设置为3个变量中的{zd0}值
int max = ( (i>j) ? ((i>k) ? i : k) : (j<k) ? j : k );
=======================================================
4.8 sizeof操作符
=======================================================
sizeof操作符的作用是返回一个对象或类型名的 字节长度 。它有三种形式:
sizeof(type name);
sizeof( object );
sizeof object;
返回值的类型是size_t,这时一种与机器相关的typedef定义,我们可以在cstddef头文件中找到它的定义。
使用:
#include <cstddef>
int ia[] = {1, 2, 3};
size_t array_size = sizeof ia; //整个数组占的字节长度:12
size_t element_size = array_size / sizeof(int); //int类型的大小
int *pi = new int[3];
size_t pointer_size = sizeof(pi); //返回指向int型的指针的字节长度,而不是pi指向的数组的长度
sizeof操作符在编译时刻计算,因此被看作是常量表达式。它可以用在任何需要常量表达式的地方:
int array[sizeof(some_type_T)]; //数组的维数
例子:
#include <string>
#include <iostream>
#include <cstddef>
int main()
{
size_t ia;
ia = sizeof( ia ); // ok
ia = sizeof ia; // ok
// ia = sizeof int; // 错误
ia = sizeof( int ); // ok
int *pi = new int[ 12 ];
cout << "pi: " << sizeof( pi )
<< " *pi: " << sizeof( *pi )
<< endl;
// 一个 string的大小与它所指的字符串的长度无关
string st1( "foobar" );
string st2( "a mighty oak" );
string *ps = &st1;
cout << "st1: " << sizeof( st1 )
<< " st2: " << sizeof( st2 )
<< " ps: " << sizeof( ps )
<< " *ps: " << sizeof( *ps )
<< endl;
cout << "short :\t" << sizeof(short) << endl;
cout << "short* :\t"<< sizeof(short*) << endl;
cout << "short& :\t"<< sizeof(short&) << endl;
cout << "short[3] :\t" << sizeof(short[3]) << endl;
cout << "char :\t" << sizeof(char) << endl;
}
编译并运行它 产生如下结果:
pi: 4 *pi: 4
st1: 12 st2: 12 ps: 4 *ps: 12 //试验得出的结果是:st1: 4 st2: 4 ps: 4 *ps: 4
short : 2
short* : 4
short& : 2
short[3] : 6
char : 1
=======================================================
4.9 new和delete表达式
=======================================================
系统为每个程序都提供了一个在程序执行时可用的内存池,成为 程序的空闲存储区 或 堆。
运行时刻的内存分配被称为 动态内存分配。在空闲存储区里分配。
动态分配有new完成。
int *pi = new int; //没有被初始化
int *pi = new int(1024);//初始化为1024
int *pia = new int[10]; //定义数组,同样没有初始化,若是类对象数组且有缺省构造函数,则依次应用在每个元素上
当对象完成了使命时,我们必须显示地把对象的内存返还给空闲存储区。用delete。但是它应该用在new分配的指针上(成对出现)。
在类对象上使用delete时,会应用其析构函数释放存储区,并还给空闲存储区。
delete pi;
delete [] pia; //释放数组
=======================================================
4.10 逗号操作符
=======================================================
逗号表达式是一系列由逗号分开的表达式。这些表达式从左向右计算。逗号表达式的结束是最右边表达式的值。
例子,{dy}个逗号表达式的值是ix,第二个是0:
int ival = (ia!=0) ? ix=get_value(), ia[index]=ix : ia=new int[sz], ia[index]=0;
=======================================================
4.11 位操作符
=======================================================
操作符 功能 用法
~ 按位非 ~expr
<< 左移 expr1 << expr2
>> 右移 expr1 >> expr2
& 按位与 expr1 & expr2
^ 按位异或 expr1 ^ expr2
| 按位或 expr1 | expr2
&= 按位与赋值 expr1 &= expr2
^= 按位异或赋值 expr1 ^= expr2
|= 按位或赋值 expr1 |= expr2
位操作符允许程序员设置或测试独立的位或位域。
如果一个对象被用作一组位或位域的离散集合,那么这样的对象成为 位向量。
C++有两种方式支持位向量:
1. 用内置整值类型来表示,如:unsigned int;
2. 标准库提供了一个bitset类,它支持位向量的类抽象。
用内置整值类型来表示:
类型可以是有符号的,也可以是无符号的,建议使用无符号类型。
操作符 功能
~ 翻转操作数的每一位
<<, >> 将其左边操作数的位想左或右移动某些位。左移符从右边开始补0;右移,无符号数则补0,有符号数补0或符号位的拷贝视具体实现定义
& 两个整值操作数。在每个位所在处与
| 两个整值操作数。在每个位所在处或
^ 两个整值操作数。在每个位所在处非,两个数只有一个含有1,则结果该位为1,否则为0
例子:
unsigned int quiz1 = 0;
quiz1 |= 1 << 27; //将第28位置成1
quiz1 &= ~(1 << 27); //将第28位置成0
bool hasPassed = quiz1 & (1<<27); //判断第28位是1还是0
=======================================================
4.12 bitset操作
=======================================================
继续上节,用bitset类进行位操作。
操作 功能 用法 说明
test( pos ) pos 位是否为1 a.test( 4 )
any() 任意位是否为1 a.any() 有一位或多位为1时,返回true,否则false
none() 是否没有位为1 a.none() 与any()相反,都为0才返回true
count() 值是1 的位的个数 a.count()
size() 位元素的个数 a.size()
[pos] 访问pos 位 a[ 4 ]
flip() 翻转所有的位 a.flip()
flip( pos ) 翻转pos 位 a.flip( 4 )
set() 将所有位置1 a.set()
set( pos ) 将pos 位置1 a.set( 4 )
reset() 将所有位置0 a.reset()
reset(pos) 将pos 位置0 a.reset( 4 )
头文件:<bitset>
定义和初始化:
bitset<32> bitvec; //缺省定义,指名位向量的长度是32,所有为被初始化为0
bitset< 32 > bitvec2( 0xffff ); //前N位被初始化为参数的相应位值
bitset< 32 > bitvec3( 012 ); //012为8进制数,将第1 和3 位的值设置为1,其它为0,同样可以用10进制数
string bitval( "1010" );
bitset< 32 > bitvec4( bitval ); //同上
我们还可以标记用来初始化bitset 的字符串的范围:
string bitval( "1111110101100011010101" );
bitset< 32 > bitvec5( bitval, 6, 4 ); //从低位开始数,从位置6开始, 长度为4: 1010
bitset< 32 > bitvec6( bitval, 6 ); //从低位开始数,从位置6开始直到{zh1}: 1010101
bitset类还支持位操作符:
bitset<32> bitvec7 = bitvec2 | bitvec3;
针对上节的例子,现在可以写成:
bitset<30> quiz1;
quiz1.set(27); //quiz1 |= 1 << 27;将第28位置成1
或:quiz1[27] = 1;
quiz1.reset(27); //quiz1 &= ~(1 << 27);将第28位置成0
或:quiz1[27] = 0;
if(quiz1[27]); //判断第28位是1还是0
或:if(quiz1.test(27))
还包括两个类型转换函数:
to_string(); //转换成string
to_ulong(); //转换成 unsigned long
=======================================================
4.13 优先级
=======================================================
操作符优先级是指复合表达式中操作符计算的顺序。
用括号把一些子表达式括起来,可以改变优先级。
结合性:
赋值操作是右结合的:
ival = jval = kval = lval
算术操作符是左结合的:
ival + jval + kval + lval
C++操作符的全集,每段中优先级相同,上段优先级高于下段:
操作符 功能 用法
:: 全局域 ::name
:: 类域 ::name
:: 名字空间域 namespace::name
. 成员选择 object.member
-> 成员选择 pointer->member
[] 下标 variable[ expr ]
() 函数调用 name(expr_list)
() 类型构造 type(expr_list)
++ 后置递增 lvalue++
-- 后置递减 lvalue--
typeid 类型ID typeid(type)
typeid 运行时刻类型 ID typeid(expr)
const_cast 类型转换 const_cast<type>(expr)
dynamic_cast 类型转换 dynamic_cast<type>(expr)
reinterpret_cast 类型转换 reinterpret_cast<type>(expr)
static_cast 类型转换 static_cast<type>(expr)
sizeof 对象的大小 sizeof object
sizeof 类型的大小 sizeof( type )
++ 前置递增 ++lvalue
-- 前置递减 --lvalue
~ 按位非 ~expr
! 逻辑非 !expr
- 一元减 -expr
+ 一元加 +expr
* 解引用 &expr
& 取地址 &expr
() 类型转换 (type)expr
new 分配对象 new type
new 分配/初始化对象 new type(expr_list)
new 分配/替换对象 new(expr_list)type(expr_list)
new 分配数组 所有的形式
delete 释放对象 所有的形式
delete 释放数组 所有的形式
->* 指向成员选择 pointer->*pointer_to_member
.* 指向成员选择 object.*pointer_to_member
* 乘 expr * expr
/ 除 expr / expr
% 取模 求余 expr % expr
+ 加 expr + expr
- 减 expr - expr
<< 按位左移 expr << expr
>> 按位右移 expr >> expr
< 小于 expr < expr
<= 小于等于 expr <= expr
> 大于 expr > expr
>= 大于等于 expr >= expr
== 等于 expr == expr
!= 不等于 expr != expr
& 按位与 expr & expr
^ 按位异或 expr ^ expr
| 按位或 expr | expr
&& 逻辑与 expr && expr
|| 逻辑或 expr || expr
?: 条件表达式 expr ? expr : expr
= 赋值 lvalue = expr
=,*=,/=,%=,+=,-=,<<=,>>=,&=,|=,^= 复合赋值 lvalue += expr 等
throw 抛出异常 throw expr
, 逗号 expr, expr
=======================================================
4.14 类型转换
=======================================================
隐式转换:
int ival = 0;
ival = 3.541 + 3;
这里3.541是double型,3是int型。C++在计算前运用“算术转换”将两个操作数转成同类型。原则是,小类型总是被提升成大类型。
本例3被提升为double型。
所以加法的结果是6.351。
赋值给ival是再转型,右边转到左边,自动按截取而不是舍入进行,小数被抛弃。
所以结果是6。
若想保住小数,使其舍入,则需要自己程序实现:
ival = 3.541 + 3 + 0.5; //保证舍入
也可以显示的先类型转换再计算:
ival = static_cast<int>( 3.541 ) + 3; //结果仍然是6,显示转换也是舍弃小数
1. 隐式类型转换:
在下列情况下会发生隐式转换:
(1)在混合类型的算术表达式中:最宽的数据类型成为目标转换类型,这也被称为 算术转换。
(2)用一种类型的表达式赋值给另一种类型的对象:目标转换类型是被赋值类型。
(3)把一个表达式传递给一个函数调用:转成形式参数的类型。
(4)从一个函数返回一个表达式,表达式的类型与返回类型不同:转成返回类型。
2.算术转换:
算术转换保证了二元操作符的两个操作数被提升为共同的类型,然后再用它表示结果的类型。原则:
(1)为防止精度损失,如果必要的话,类型总是被提升为较宽的类型。
(2)所有含有小于整型的有序类型的算术表达式,在计算之前,其类型都会被转换成整型。包括:char、singed char、unsigned char、short int,及枚举型enum。
如:
3.14159L + 'a'; //3.14159L是long double型,‘a’先被转成long bouble(ASC码为97)再计算
3. 显示转换
也被称为 强制类型转换(cast),包括下列操作符:
static_cast、dynami_cast、const_cast、reinterpret_cast。
显示转换之后,编译器则不会发出提示信息了(否则有警告)。
一般形式:
cast-name<type>(expression);
显示转换存在的原因:
(1)将void*强制转成显示类型的指针
(2)希望改变通常的标准转换
(3)要避免出现多种转换可能的歧义情况
如:
const char *pc_str;
char *pc = pc_str; //编译出错
char *pc = const_cast<char*>(pc_str); //ok
行为不佳的静态转换(即那些有潜在危险的类型转换):
将void*型的指针强制转换成某种显式指针类型
把一个算术值强制转换成枚举型
把一个基类强制转换成其派生类或者这种类的指针或引用
在引入这些强制转换操作符之前,显式强制转换由一对括号来完成:
char *pc = (char*) pcom; //与reinterpret_cast效果一样
4. 旧式强制类型转换
有两种形式:
type (expr); //C++语言强制转换符号
(type) expr; //C语言强制转换符号
标准C++对旧式强制转换符号的支持,是为了保持 向后兼容性。
如果我们希望自己的代码在C++和C语言中都能编译的话,那么只能使用C语言的强制转换符号。
但是建议使用新式强制转换符号。
=======================================================
4.15 栈类实例
=======================================================
栈是计算机科学的一个基本数据抽象,它允许以 后进先出 的顺序嵌入和获取其中的值。
代码略。