C++ primer 5th 第4章 表达式
=====================================================================
第4章 表达式 122页
=====================================================================
//QQ108201645编写
/*根据运算符优先级,表达式3+4*5的值是23,不是35
根据运算符的结合律,表达式20-15-3的值是2,不是8
举个稍复杂的6+3*4/2+2在C++语言中是14,因为与
(6+((3*4)/2))+2)。*/
#include<iostream>
using namespace std;
int main()
{
cout << (6 + 3)*(4 / 2 + 2) << endl;//输出36
cout << ((6 + 3) * 4) / 2 + 2 << endl;//输出20
cout << 6 + 3 * 4 / (2 + 2) << endl;//输出9
system("pause");
return 0;
}
=====================================================================
//QQ108201645编写
#include <iostream>
#include <vector>
#ifndef TEST
#define TEST
#endif
using namespace std;//使用命名空间
int main()
{
int ia[] = { 0,2,4,6,8 };//含有5个整数的数组
int last = *(ia + 4);/*把last初始化成8,就是ia[4]*/
last = *ia + 4;//last=4,等价于ia[0]+4
//如果想访问ia+4的元素那加法运算两端是不可少,一旦去掉这对括号
//*ia就会首先组合在一起,然后4再与*ia的值相加
#ifndef TEST
int v1, v2;
cin >> v1 >> v2;//先读入v1再读入v2
#endif
//练习题4.1(5+10*20/2)
cout << 5 + 10 * 20 / 2 << endl;//打印输出结果:105
//练习题4.2根据4.12节中的表,在下列表达式的合理位置添加括号,使用添加括号后运算对象的组合顺序一致
vector<int> vec{ 1,2,3,4,5 };
auto it = vec.begin();
*(vec.begin()) = 20;//vec.begin()等价于it,所以*(vec.begin())等价于*it
cout << "第一次输出" << endl;
for (; it != vec.end(); ++it)
{
cout << *it << " ";
}
cout << "\n第二次输出" << endl;
*(vec.begin()) + 1;
for (it = vec.begin(); it != vec.end(); ++it)
{
cout << *it << " ";
}
#ifndef TEST
(a) :*vec.begin() 修改为*(vec.begin())
(b) : *vec.begin() + 1 修改为*(vec.begin()) + 1
#else
system("pause");
#endif
return 0;
}
=====================================================================
第4章 表达式 122页 求值顺序
=====================================================================
//QQ108201645编写
#include <iostream>
#include <vector>
using namespace std;//使用命名空间
int f1()
{
cout << "f1()..." << endl;
return 10;
}
int f2()
{
cout << "f2()..." << endl;
return 20;
}
int main()
{
int i = f1()*f2();
cout << "i = "<< i << endl;
i = 0;
cout << "i = " << i << endl;
cout <<"i = "<< i << "\t ++i = " << ++i << endl;//未定义的
int j = i++;
cout << "i = " << i << "\t j = " << j << endl;
j = ++i;
cout << "i = " << i << "\t j = " << j << endl;
/*逻辑与(&&)运算符,先求左侧对象为真的话,才继续求右侧对象的值。当两个都为真时才为真
逻辑或(||)运算符,只有当两个中的一个为真,结果就为真*/
system("pause");
return 0;
}
=====================================================================
表4.1:算术运算符 | ||
运算符 | 功能 | 用法 |
+ | 一元正号 | + expr |
- | 一元负号 | - expr |
* | 乘法 | expr * expr |
/ | 除法 | expr / expr |
% | 求余 | expr % expr |
+ | 加法 | expr + expr |
- | 减法 | expr - expr |
=====================================================================
第4章 表达式 124页 求值顺充、优先级、结合律
=====================================================================
//QQ108201645编写
#include <iostream>
#include <vector>
using namespace std;//使用命名空间
int main()
{//一元负号运算符对运算对象值取负后,返回其(提升后)副本
int i = 1024;
int k = -i;//k是-1024
bool b = true;
bool b2 = -b;//b2是true
//溢出和其它算术Arithmetic运算异常
short short_valua = 32767;//如果short类型占16位,则能表示最大值是32767
short_valua += 1;//该计算arithmetic导致溢出
cout<<"short_value: "<<short_valua<<endl;
//输出:short_value: -32768
//该值发生了一环绕(wrapped around),符号位本来是0,由于溢出被改成了解1,
//于是结果变成负值,在别的系统中也许会有其它结果,程序的行为可能不同甚至崩溃
//算术arithmetic运算符+,-,*,/的含义分别是加法,减法,乘法,除法。整数相除
//结果还是整数,就算有小数点也会被直接弃除
int ival1 = 21 / 6;//ival1是3,结果进行了删节,余数被抛弃掉了
int ival2 = 21 / 7;//ival2是3,没有余数,结果是整数值
//运算符%俗称 取余 或 取模 运算符,负责计算arithmetic两个整数相除所得的余数,
//参与运算的对象必须是整数类型
int ival = 42;
double dval = 3.14;
ival % 12;//正确。结果是6
//ival% dval;//错误:运算对象是符点型
int m = 5, n = 5;
int result = (m / n)*n + m % n;//结果为5
cout << result << endl;
m = 5, n = 4;
result = (m / n)*n + m % n;//结果为5
cout << result << endl;
//C++语言的早期版本允许m%n的符号匹配n的符号,而且商向负无穷一侧取整,这一方式
//在新标准中已经被禁止使用,除了-m导致溢出的特殊情况外,其它时候(-m)/n和m/(-n)
//都等于-(m/n),m%(-n)等于m%n,(-m)%n等于-(m%n).具体示例如下
/*
21 % 6 结果是3 21 / 6 结果是3
21 % 7 结果是0 21 / 7 结果是3
-21 % -8 结果是-5 -21 / -8 结果是2
21 % -5 结果是1 21 / -5 结果是-4
*/
//练习题4.4在下面的表达式中添加括号,说明其求值的过程及最终结果。编写程序编译不加括号的表达式
//12 / 3 * 4 + 5* 15+ 24%4 /2
cout << 12 / 3 * 4 + 5 * 15 + 24 % 4 / 2 << endl;//91
system("pause");
return 0;
}
=====================================================================
表4.2:逻辑运算符和关系运算符 | |||
结合律 | 运算符 | 功能 | 用法 |
右 | ! | 逻辑非 | !expr |
左 | < | 小于 | expr < expr |
左 | <= | 小于等于 | expr <= expr |
左 | > | 大于 | expr > expr |
左 | >= | 大于等于 | expr >= expr |
左 | == | 相等 | expr == expr |
左 | != | 不相等 | expr != expr |
左 | && | 逻辑与 | expr && expr |
左 | || | 逻辑或 | expr || expr |
=====================================================================
第4章 表达式 127页 逻辑与和逻辑或运算符
=====================================================================
//QQ108201645编写
#include <iostream>
#include <vector>
#include <cctype>
#include<string>
using namespace std;//使用命名空间
int main()
{
//对于 (&&)逻辑与 运算符,仅当左侧运算对象为真时才对右侧运算对象求值
//对于 (||)逻辑或 运算符,仅当左侧运算对象为假时才对右侧运算对象求值
vector<string>text{ "abc.","123.","ABC." };
for (const auto &s : text)
{
//对于text的每个元素
cout << s;//输出当前个数
//遇到空字符串或者以句话结束的字符串进换行
if (s.empty()||s[s.size()-1]=='.')
{//检查左侧对象非空时才进行右侧对象求值,检查string对象是否是以句号结束的
//利用逻辑或运算符的短路求值策略确保当s非空时才会用下标去访问它
//因为text元素是string,可能非常大,所以声明成引用可以避免对元素的拷贝
//又不需要对string对象写操作,所以被声明成对常量的引用
cout<<endl;
}
cout << " ";//否则用空格隔开
}
vector<int> vec{ 1,2,3,4 };
if (!vec.empty())//当对象不为空时输出首元素
{
cout << vec[0];
}
//!vec.empty()当empty函数返回假时结果为真
//关系运算符
int i = 0, j = 1, k = 2;
//128页:这个居然拿i<j的布尔值结果与k比较
if (i < j < k)//若k大于1则为真
{
cout<<i<< " "<< j <<" "<< k<<endl;
}
//正确的写法
if (i < j&&j < k) { ; }//当i小于j并且j小于k时,条件为真
int val = 0;
if (val) { ; }//如果val是任意的非0值,为真
if (!val) { ; }//如果val是0,条件为真
if (val == true) { ; }//只有当val等于1时,条件为真
if (val == 1) { ; }//同上:直观
//练习题4.8
/*逻辑与:先算左侧表达式结果为真时,才需要右侧对象的求值
逻辑或:先算左侧表达式结果不为真时才需要右侧表达式的求值
相等性运算符中左侧结果与右侧相等结果为真,不相等为假*/
//练习题4.9解释下面if语句中条件部分的判断过程
const char *cp = "Hello World";
if (cp&&*cp) { cout << "True" << endl; }
//判断左侧的地址 是否为空,不是空返回真。右侧地址里的值是否为空不为空返回真
//练习4.10为while循环写一个条件,使其从标准输入中读取整数,遇到42时停止
int bcin;
while (cin >> bcin && bcin != 42) { cout << bcin << endl; }
//练习题4.11书写一条表达式测试4个abcd的关系,确保a大于b,b大于c,c大于d
int a = 4, b = 3, c = 2, d = 1;
if ((a > b&&b > c) && (b > c&&c > d))
{ cout << a << " " << b << " " << c << " " << d << endl; }
//练习4.12
//i!=j<k的含义是:
//j是不是小于k是的话返回真,否返回假的结果是不是与i不相等,是的话结果为真,反之为假
system("pause");
return 0;
}
=====================================================================
第4章 表达式 129页 赋值运算符
=====================================================================
//QQ108201645编写
#include <iostream>
#include <vector>
#include <cctype>
#include<string>
using namespace std;//使用命名空间
int get_value(int num);//声明函数
int main()
{
int i = 0, j = 0, k = 0;//初始化而非赋值
const int ci = i;//初始化而非赋值
//下面的赋值语句都是非法的
/*
1024 = k;//错误:字面值是右值
i + j = k;//错误:算术arithmetic表达式expression是右值
ci = k;//错误:ci是常量,不可修改的左值
*/
//赋值运算的结果是它的左侧运算对象,并且是一个左值,相应的
//类型就是左侧运算对象的类型。如果赋值左右两个运算对象类型
//不同,则右侧运算对象将转换成左侧运算对象的类型
k = 0;//类型:int 结果0
k = 3.1415926;//类型 int 结果3
//C++新标准允许使用花括号括起来的初始值列表,做为赋值语句的右侧运算对象
//k = { 3.14 };//错误,窄化转换
vector<int> vi;//初始为空
vi = { 0,1,2,3,4,5,6,7,8,9 };//vi现在含有10个元素了,值从0到9
//如果左侧是内置类型如bool,char,int,float,double等。初始最多只能包含一个值
//赋值运算满足右结合律
#ifdef IVAL//ival照书中连续重定义,是不合法的,所以不执行下面这段
int ival, jval;
ival = jval = 0;//正确,都被赋值为0;
//因为赋值运算满足右结合律,所以靠右的赋值jval=0作为靠左的赋值运算符的右侧对象
//又因为赋值运算返回的是其左侧运算对象,所以靠右的赋值运算结果(既jval)被赋值
//给了ival
#endif//ival照书中连续重定义,是不合法的,所以不执行上面这段
int ival, *pval;//ival的类型是int,pval是指向int的指针
//ival = pval = 0;//错误,不能把指针的值赋给int
//因为ival和pval的类型不同,而且pval的类型int*无法转换成ival的int类型
//尽管0这个值能赋给任何对象,但是把int*指针地址赋值给int仍然是非法的
string s1, s2;
s1 = s2 = "OK";//字符串字面值值"OK"转换成string对象
//这是一种形式烦琐、容易出错的写法
int num = get_value(5);//得到第一个值
while (num != 42) { num = get_value(num); }//得到剩下的值
num = 0;
while ((num = get_value(num)) != 42) { cout << num << " "; }//更好的写法。表达更加清楚
//要加左侧的小括号,否则会陷入无限循环
system("pause");//
return 0;
}
int get_value(int num)//定义函数
{
return ++num;
}
=====================================================================
第4章 表达式 130页 复合运算符优先级相关
=====================================================================
//QQ108201645编写
#include <iostream>
#include <vector>
#include <cctype>
#include<string>
using namespace std;//使用命名空间
int main()
{
//C++允许赋值运算作为条件,但这一特性可能带来意想不到的后果
int i = 0, j = 0;
//切勿混淆相等运算符与赋值运算符
if (i = j) { ; }//if中把j的值赋给了i,然后检查赋值结果是否为真,j如果不为0
//条件将为真,然后程序员的初衷很可能是想判断i与j是否相等
if (i == j) { ; }
//复合赋值运算符
int sum = 0;
//计算从1到10(包含10在内)的和
for (int val = 1; val <= 10; ++val)
sum += val;//等价于sum=sum+val;
/*这种复合操作不仅对加法来说很常见,也常常应用于其他算术arithmetic或者4.8节
第135页将要介绍的位运算符。每种运算符都有相应的复合赋值形式*/
/*任意一种复合运算符都完全等价于a = a op b*/
//唯一区别就是左侧运算对象的求值次数,使用复合运算符只求值一次。使用普通运算符
//则求值两次,这两次包括;一次是作为右边表达式的一部分求值,另一次是作为赋值运算的左侧
//运算对象求值。其实在很多地方,这种区别除了对程序性能有些许影响外几乎可以忽略不计
//练习题4.13在下述语句中,当赋值完i和d的值分别是多少
/*
+= -= *= /= %= 算术运算符
<<= >== &= ^= |= 位运算符
*/
int ival; double d;
d = ival = 3.5;//运算顺序ival=3赋给int类型丢失小数点部分,d=3
ival = d = 3.5;//d=3.5,ival=3
//练习题4.14执行下述if将发生什么
//if (42 = i) { ; }//错误,左值必须为普通变量
if (i = 42) { ; }//正确把42赋值给i,i大于0结果为真
//练习题4.15下面的赋值是非法的,为何,如何修改
double dval1;
int ival1;
int *pi;
//dval1 = ival1 = pi = 0;//错误,把指针地址初始化为0后,又把int*指针地址赋值给int类型,指针类型无法转换成整型
//修改方式
dval1 = ival1 = 0, pi = 0;
//练习题4.15尽管下面的语句合法。但执行可能与预期不一样,为什么,应该如何修改
//(a) if(p=getPtr()!=0) (b)if(i=1024)
//修改为if((p=petPtr())!=0),if(i==1024)
system("pause");
return 0;
}
=====================================================================
第4章 表达式 131页 递增与递减运算符
=====================================================================
#include <iostream>
#include <vector>
#include <cctype>
#include<string>
using namespace std;//使用命名空间
int main()
{
/*递增运算符(++)和递减运算符(--)为对象的加1减1 操作提供了一种
简洁的书写形式。这两个运算符还可应用于迭代器,因为很多迭代器本身不支持算术
运算,所以此时递增与递减运算符除了书写简洁外还是必须的*/
/*有两种形式:前置版本与后置版本本书使用都是前置版本,这种形式先将运算对象
自身加1(或减1),然后将改变的对象作为求值结果,后置版本也会将运算对象加1(减1),
但是求值结果是运算对象改变之前的那个值的副本*/
int i = 0, j;
j = ++i;//j=1,i=1:前面版本先运算++i得到递增之后的值赋给j
j = i++;//j=1,i=2:兵置版本先把i的值赋给j,得到递增之前的值,再把i递增
//这两种必须作用于左值运算对象,前置版本将对象本身作为左值返回,后置版本
//则将原始值的副本作为右值返回
//如果想在一条复合表达式中既将变量加1或减1又能使用它原来的值,这时就可以使用
//递增和递减的后置版本
vector<int> v{ 1,2,3,4,5 };
auto pbeg = v.begin();
while (pbeg != v.end() && *pbeg >= 0)
{ cout<<*pbeg++<<endl; }//输出当前值并将pbeg向前移动一个元素
//后置递增运算符的优先级高于解引用运算,因此*pbeg++等价于*(pbeg++)
//pbeg++把pbeg的值1,然后返回初值的副本作为其求值的结果
//此时解引用运算符的运算对象是pbeg未增加之前的值。最终,这条语句输出pbeg开始时指向的那个元素
//并将指针向前移动一个位置
//既这种用法是基于一个事实,既后置递增运算符返回初始的未加工厂的值。如果返回的是加工厂
//之后的值,解引用将产生错误的结果。不但无法输出第一个元素,而且更糟糕的是如果序列
//中没有负值,程序将可能试图解引用一个根本不存在的元素
auto iter = v.begin();
cout<<*iter++<<endl;
//要比下面这句更简洁
cout<<*iter<<endl;
++iter;
system("pause");
return 0;
}
=====================================================================
第4章 表达式 131页 运算对象可按任意顺序求值
=====================================================================
#include <iostream>
#include <vector>
#include <cctype>
#include<string>
using namespace std;//使用命名空间
int main()
{
vector<char> s( 5,'a' );
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
{
*it = toupper(*it);//将当前字符改成大写
cout << *it << " ";
}
cout<<endl;
//解引用it和递增it两项任务分开来完成,如果用一个看似等价的while来代替
//因为上面改成过大写了,所以下面转小写
auto beg = s.begin();
while (beg!=s.end()&&!isspace(*beg))
{
//*beg = tolower(*beg++);//错误。该赋值语句未定义
//将产生未定义的行为。问题是运算符左右两端的运算对象都用到了beg,并且右侧的
//运算对象还改变了beg的值,所以该赋值语句是未定义的,编译会按下面的任意一种思路
/*处理该表达式
*beg=tolower(*beg);//如果先求左侧的值
*(beg+1)=tolower(*beg);//如果先求右侧的值
*/
*beg = tolower(*beg);//要改成这样
cout << *beg << " ";
beg++;//这里加一个++向前移动
}
//练习题4.17说明前置递增与后置递增运算符的区别
//前置版本对对象作出改变后,返回改变后对象本身,是一个左值,而后置版本改变对象后,返回是对象改变前的副本,是一个右值
system("pause");
return 0;
}
=====================================================================
#include <iostream>
#include <vector>
#include <cctype>
#include<string>
using namespace std;//使用命名空间
int main()
{//练习题4.18如果第132页那个输出vector对象元素的while使用前置递增将得到什么结果
vector<char> s( 5,'a' );
auto beg = s.begin();
/*while (beg!=s.end()&&!isspace(*beg))
{
*beg = toupper(++*beg);//从下一个元素开始解引用,将引用最后位置的下一个位置导致溢出无限循环
cout << *beg << " ";
//beg++;//把上面++去掉。这里加一个++向前移动
}*/
//练习题4.19假设ptr的类型是指向int 的指针,vec的类型是vecot<int>、ival的类型是int
//说明下列表达式的含义,如果有不正确,为何,如何修改
vector<int> vec{ 0,1,2,3,4,5,6,7,8,9 };
int ival1, *ptr=&ival1;
ival1 = (ptr != 0 && *ptr++);
//判断ptr的指针是否为空,为0,结果为假。不为0的话,判断右边值是否为0,为0则为假,不为0则为真。然后右侧地址++
//另一种解释:先判断ptr是否为空指针,若不为空,先将ptr递增使其指向下一个int元素,然后对递增前的ptr解引用,并判断解引用后的值是否为0
ival1 = (ival1++&&ival1);
//左侧ival1的值否不为0,是的话右侧是否也不为0,都不为0,结果为真,然后左侧++
//另一种解释:先将ival的值自增加1,然后判断自增前的值是否为0,不为0则再判断自增后的值是否为0
ival1 = vec[ival1++] <= vec[ival1];
//其他解释:C++没有规定<=运算符的求值顺序,应修改为vec[ival]<=vec[ival+1]
system("pause");
return 0;
}
=====================================================================
第4章 表达式 134页 成员访问运算符
=====================================================================
//QQ108201645编写
#include <iostream>
#include <vector>
#include <cctype>
#include<string>
using namespace std;//使用命名空间
int main()
{
string s1 = "a string", *p = &s1;
auto n = s1.size();//运行string对象的s1的size成员
n = (*p).size();//运行p所指对象的size成员
n = p->size();//等价于(*p).size
//因为解引用运算符的优先级低于点运算符,所以执行解引用运算的子表达式两端必须加上括号
//如果没有括号,代码含义就不一样了
//*p.size();//错误:p是一个指针,它没有名为size的成员
//这条表达式试图访问对象p的size成员,但是p本身是一个指针,且不包含任何成员,所以无法通过编译
//箭头运算符作用于一个指针类型的运算对象,结果是一个左值,点运算符分成两种,如果
//对象成员所属的对象是左值,那么结果是左值。反之,成员所属对象是右值,结果是右值
//练习题4.20:假设iter的类型是vector<string>::iterator,说明下湎表达式是否合法
/*
a: *iter++ b: (*iter)++; c: *iter.empty();
d: iter->empty() e: ++*iter ; f: iter++->empty()
*/
vector<string> svec{ "abc","def","ghi","jkm" };
vector<string>::iterator iter = svec.begin();
*iter++;//iter的地址++,指向下一个元素,解引用++前的迭代器
//(*iter)++;//不合法,字符串没有++运算
//*iter.empty();//不合法,访问iter的成员empty,但iter是迭代器
iter->empty();//检查元素是否为空
//++*iter;不合法,字符串没有自增运算
iter++->empty();//合法,将iter指向下一个元素,检查移动前的值是否是空字符
system("pause");
return 0;
}
=====================================================================
#include <iostream>
#include <vector>
#include <cctype>
#include<string>
using namespace std;//使用命名空间
int main()
{
//条件运算符(?:),允许把简单的if-else逻辑嵌入到表达式当中,条件运算符按照形式如下
//condition ? expression : expression;
int grade = 0;
string finalgrade = (grade < 60) ? "fail" : "pass";
#ifdef DECLARATION
条件部分判断成绩是否小于60,如果小于,表达式结果是fail赋值给finalgrade,否则是把pass赋值给finalgrade
有点类似于(&&和||)条件运算符只对expr1和expr2的一个求值
当条件运算符的两个表达式都是左值或者能转换成同一种左值类型时,运算的结果是左值,否则运算结果是右值
*左值与右值这两概念是从 c 中传承而来的,在 c 中,左值指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式),
*右值指的则是只能出现在等号右边的变量(或表达式).
#endif
//嵌套条件运算符
finalgrade = (grade > 90) ? "high pass" :
(grade < 60) ? "fail" : "pass";
//第一个条件检查成绩是否在90分以上,如果是,执行?后面的表达式,得到"high pass"
//如果否,执行第一个符号:后面的分支。这个分支本身就是一个条件表达式,它检查成绩是否在
//60以下,如果是,得到"fail",否则就得到"pass"
//条件运算符满足右结合律,意味着运算对象(一般)按照从右向左的顺序组合。因此在上面的代码
//中,靠右边的条件运算(比较成绩是否小于60)构成了靠左边的条件运算的:分支
//在输出表达式中使用条件运算符
//条件运算符优先级非常低,所以通常要加括号,如果这不加括号可能有意想不到的效果
cout << ((grade < 60) ? "fail" : "pass")<<endl;//输出pass或fail
//cout << (grade < 60) ? "fail" : "pass" ;//输出1或0
/*在第二条表达式中,grade和60的比较结果是<<运算符的运算对象,因此如果grade<60
为真输出1,否则输出0。<<运算符的返回值是cout,接下来cout作为运算条件运算符的条件等价于
cout<<(grade<60);//输出1或0
cout?"fail":"pass"//根据cout的值是true还是false产生对应字面值
*/
//cout<<grade<60?"fail":"pass";//错误:试图比较cout与60
/*第三条表达式等价于下面的语句,所以它是错误的
cout << grade;
cout << 60 ? "fail" : "pass";//然后比较cout和60
*/
//练习4.21编写一段程序,使用条件运算符从vector<int>中找到哪些元素的值是奇数
vector<int> ivec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
for (auto &c:ivec)
if (c%2!=0)
{
cout << c << " ";
c *= 2;
}
cout<<endl;
for (auto c : ivec)
cout << c << " ";
cout<<endl;
//练习题4.22本节示例将成绩分成high pass,pass和fail三种,扩展该程序使其进一步将60分到75分之间的
//程序设定为low pass。要求程序包含两个版本,一个只使用条件运算符
//另一个使用1个或多个if语句,哪个版本更容易理解
grade = 80;
cout << (grade > 95 ? "high pass" ://当grade大于95时是"high pass
grade >= 60 && grade <= 75 ? "low pass" ://当grade大等于60并且小等于75时,结果是"low pass"
grade < 60 ? "fail" : "pass") << endl;//当grade小于60时,结果是"fail"。
//如果既不是大于95,也不是在60到75之间,也不小于60那结果就是"pass"
if (grade>95)
{
cout<<"high pass"<<endl;
}
else if (grade>=60&&grade<=75)
cout<<"low pass"<<endl;
else if (grade<60)
cout<<"fail"<<endl;
else
{
cout<<"pass"<<endl;
}
//练习题4.23因为运算符的优先级问题,下面这条表达式无法通过编译。根据4.12节第147页
//指出它的问题在哪,如何修改
string s = "word";
//string p1 = s + s[s.size() - 1] == 's' ? "" : "s";//错误,加法运算符优先级比相等运算符高导致运算出错
string p1 = s +( s[s.size() - 1] == 's' ? "" : "s");
//练习题4.24本节示例将成绩分成high pass,pass和fail三种,它的依据是条件运算符满足右结合律,
//假设条件运算符满足的是左结合律,求值过程是怎么样的
#ifdef DECLARATION
如果条件运算符满足的是左结合律。
示例程序将会被解读成 finalgrade = ((grade > 90) ? "high pass" : (grade < 60)) ? "fail" : "pass"; 先判断grade > 90是否成立,根据返回值来执行"high pass"或者grade < 60, 然后将"high pass"或grade < 60中的一个作为最后的条件来判断。
#endif
system("pause");
return 0;
}
=====================================================================
表4.3:位运算符(左结合律) | ||
~ | 位求反 | ~expr |
<< | 左移 | expr1 << expr2 |
>> | 右移 | expr1 >> expr2 |
& | 位与 | expr & expr |
^ | 位异或 | expr ^ expr |
| | 位或 | expr | expr |
=====================================================================
第4章 表达式 136页 位运算符
=====================================================================
//QQ108201645编写
#include <iostream>
using namespace std;//使用命名空间
int main()
{//下面的图例中右侧最低位并且假定char占8位、int占32位
unsigned char bits = 0223;//输入八进制的223,就是十六进制的0x93,二进制是10 01 00 11
bits << 8;//bits提升成int类型,然后向左移动8位, 附1
bits << 32;//向左移动31位,左边超出边界的位丢弃掉了,附2
bits >> 3;//右移3位,最右边的丢弃掉了,附3
system("pause");
return 0;
}
=====================================================================
注:为表示位数,加入空格间隔,实际上二进制之间没有空隔的
附1 | 00 00 00 00 | 00 00 00 00 | 10 01 10 11 | 00 00 00 00 |
附2 | 10 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 |
附3 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 01 00 11 |
左移运算符(<<)在右侧插入值为0的二进制。右移运算符(>>)的行为则依赖于其左侧运算对象是带符号类型,在左侧插入符号位的副本或值为0的二进制位,如何选择视情况而定
=====================================================================
第4章 表达式 137页 位运算符
=====================================================================
//位求反运算符( ~ )将运算符逐位求反后生成一个新值,将1置0、将0置1
//QQ108201645编写
#include <iostream>
using namespace std;//使用命名空间
int main()
{//下面的图例中右侧最低位并且假定char占8位、int占32位
unsigned char bits = 0227;//输入八进制的227,就是十六进制的0x97,二进制是10 01 01 11
//当使用~后;
~bits;//二进制结果11 11 11 11 .11 11 11 11.11 11 11 11.01 10 10 00
//char类型的运算对象首先提升成int类型,提升时运算对象原来的位保持不变,往高位
//(high order position)添加0即可,因此在本例中,首先将bits提升成int类型,
//增加24个高位0,随后将提升后的值逐位求反
// 00 00 00 00 .00 00 00 00. 00 00 00 00 .10 01 01 11
//求反后
// 11 11 11 11 .11 11 11 11. 11 11 11 11. 01 10 10 00
//附计算器说明
system("pause");
return 0;
}
=====================================================================
//打开自带计算器
10 01 01 11就是八进制227的二进制表示数
转十六进制就是0x97
上图的分为两行,第一行的1是属于64位类型的,第二行是32位类型的。一般只用到32位
当点完求反按钮后,就求反变成了
求反前 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 10 | 01 | 01 | 11 |
求反后 | 11 | 11 | 11 | 11 | 11 | 11 | 11 | 11 | 11 | 11 | 11 | 11 | 01 | 10 | 10 | 00 |
用进制之间的转换可以清楚的看清求反前后的结果
=====================================================================
#include <iostream>
using namespace std;//使用命名空间
int main()
{
//与( & )、或( | )、异或( ^ )运算符在两个运算对象上逐位执行相应的逻辑操作
unsigned char b1 = 0145;//二制制表示数 01100101
unsigned char b2 = 0257;//二制制表示数 10101111
b1 & b2;//24个高阶位都是0二制制表示数00100101 口诀(两个变量运算:见0出0,全1出1)
b1 | b2;//24个高阶位都是0二制制表示数00100101 口诀(两个变量运算:见1出1,全0出0)
b1 ^ b2;//24个高阶位都是0二制制表示数00100101 口诀(两个变量运算:相同出0,不同出1)
//对于位运算符(&)来说,如果相个运算对象转二进制后对应位置都是1则运算结果中该位为1,否则
//为0,对于位运算符(|)来说,如果两个运算对象转二进制的对应位置至少有一个为1时则运算结果为1
//否则为0。对于位运算符(^)来说,如果两个运算对象转二进制后位置其中且只有一个1,则运算结果中该位为1
//否则为0
cout << (1 & 2) << endl;//输出为0,二进制00 00 00 01对应00 00 00 10流程01对10,见0出0则为0
cout << (1 | 2) << endl;//输出为0,二进制00 00 00 01对应00 00 00 10流程01对10,见1出1所以是11十进制是3
cout << (1 ^ 2) << endl;//输出为0,二进制00 00 00 01对应00 00 00 10流和01对10,相同出0,不同出1所以为11转十进制是3
cout << (1 ^ 0) << endl;//结果为255;
//注意:常见错误是把位运算符和逻辑运算符搞混了,比如位与(&)和逻辑与(&&)、位或(|)和逻辑或(||)
//位求反(~)与逻辑非(!)
system("pause");
return 0;
}
=====================================================================
第4章 表达式 137页 使用位运算符
=====================================================================
//QQ108201645编写
#include <iostream>
#include <string>
#include <cstddef>//size_t所在文件头
#include "Sales_data.h"
using namespace std;//使用命名空间
#ifndef DECLARATION
#define DECLARATION
//说明部分
//执行已定义声明的话将不再执行
#endif
int main()
{
unsigned long quiz1 = 0,UL1=1;//我们把这个值当成是位的集合
cout<<(UL1<<27)<<endl;//生成一个值,该位只有27位为1
quiz1 |= UL1 << 27;//表示27通过了测试//quiz1的27位经过(见1出1,全0出0)异或后结果为1
//等价于quiz1 = quiz1 | UL1 << 27;
cout<<quiz1<<endl;
quiz1 &= ~(UL1 << 27);//学生27没有通过测试
// UL1左移27位取反后就27位为0,其它为1。与原先27位为1的quiz1进行相与(见0出0,全1出1)操作,结果为0
cout << quiz1<< endl;
bool status = quiz1 & (UL1 << 27);//学生是否通过了测试
//最后把27位为0的quiz1再和左移27位的UL1进行相与(见0出0,全1出1)结果为0
cout << status << endl;
//移位运算符
cout<<"hi"<<" there"<<endl;
//执行过程等同于((cout<<"hi")<<" there")<<endl;
//运算对象"hi"与第一个<<组合在一起,它的结果与第二个<<组合在一起,接下来的结果再和第三个组合在一起
//移位运算符优先级不高不低,介于中间,比算术运算符的优先级低,但比关系运算符、赋值、
//和条件运算符的优先级高,在一次使用个运算符时,有必要适当的地方加上括号
cout << 42 + 10;//正确:+的优先级更高,因此输出求和结果
cout << (10 < 42);//正确:括号使运算对象按照我们的期望组合在一起,输出1
//cout << 10 < 42;//错误:试图比较cout和42
//等价于(cout<<10)<42;把数字10写到cout,然后结果(既cout)与42进行比较
//练习4.25如果一台机器上int占32位、char占8位,用的是Latin-1字符集
//其中字符'q'在二进制形式是01110001,那么表达式'q'<<6的是值是什么
cout << endl;
char c = 'q';
int num = 0;
memcpy((char*)&num, &c, 1);//复制一个字节的char c到int num的第一个字节
cout << num << endl;//int值输出113
num <<= 6;//左移位后
cout << num << endl;//int值输出7232
//练习4.26在本节测试的例子中,如果使用unsigned int作为quiz1的类型会发生什么
unsigned int num1 = (UL1 << 27);
cout<<hex<<num1<<endl;
//练习4.27下列表达式结果是什么
unsigned long ul1 = 3, ul2 = 7;
cout << (ul1&ul2) << endl;//进行 位与 操作结果是011与111相与结果是11转十六进制是3
cout << (ul1|ul2) << endl;//进行 位或 操作结果是011与111相或结果是111转十六进制是7
cout << (ul1&&ul2) << endl;//进行 逻辑与 操作是两边都是非0,结果为1
cout << (ul1||ul2) << endl;//进行 位与 操作有一边非0,结果是1
#ifndef DECLARATION//如果已经定义过就不执行下面
sizeof运算符返回一条表达式或一个类型名称所占的字节数。sizeof运算符满足
右结合律,所得值是一个size_t类型(参见3.5.2节,第103页)的常量表达式(参
见2.4.4节,第58页)。运算符的运算对象有两种形式
sizeof (type)
sizeof expr
在第二种形式中,sizeof返回的是表达结果类型的大小,与众不同的一点是,sizeof
并不实际计算其运算对象的值
#endif
//这里要加入原先的头文件Sales_data.h
#ifndef DECLARATION//如果已经定义过就不执行下面
//把下面的另存一个头文件,方面下面的能调用到
//开始
//保存为Sales_data.h头文件
#ifndef _SALES_DATA_H_
#define _SALES_DATA_H_
struct Sales_data
{
std::string bookNo;
unsigned units_sold = 0;//c++11开始支持类体内初始化(变量名译:单独售出)
double revenue = 0.0;//初始化revenue(译:财政收入)
};//这里要加分号
#endif //_SALES_DATA_H_
#endif //DECLARATION
Sales_data data, *ps;
sizeof(Sales_data);//存储Sales_data类型对对象所占的空间大小
sizeof data;//data的类型的大小,即sizeof(Sales_data);
sizeof ps;//指针所占的大小
sizeof *ps;//p所指类型的空间大小,即sizeof(Sales_data)
sizeof data.revenue;//Sales_data的revenue成员所对应类型的大小
sizeof Sales_data::revenue;//另一种获取Sales_data的revenue大小的方式
cout<<"sizeof(Sales_data) = "<<sizeof(Sales_data)<<endl;
cout<<"sizeof data = "<<sizeof data<<endl;
cout<<"sizeof ps = "<<sizeof ps <<endl;
cout<<"sizeof *ps = "<<sizeof *ps <<endl;
cout<<"sizeof data.revenue = "<<sizeof data.revenue<<endl;
cout<<"sizeof Sales_data::revenue = "<<sizeof Sales_data::revenue<<endl;
#ifndef DECLARATION
这些例子中最有趣是sizeof *p。因为sizeof满足右结合律并且与*运算符的优先级一样,所以表达式
按照从右向左的顺序结合,它等价于sizeof(*p)。其次人,因为sizeof不会实际求运算对象的值
所以即使p是一个无效的(即使未初始化的)指针(参见2.3.2,第47节)也不会有什么影响。在sizeof
的运算中解引用一个无效指针仍然是一种安全的行为,因为指针实际上并没有被真正使用。sizeof不需要
真的解引用指针地址也能知道它所指的对象的类型
sizeof运算符的结果部分依赖于其作用的类型
对char或者类型为char的表达式执行sizeof运算,结果为1
对引用类型执行sizeof运算得到被引用对象所占空间的大小
对指针执行sizeof运算得到指针本身所占空间的大小
对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小,指针不需有效
对数组执行sizeof运算得到整个数组所占空间大小,等价于对数组中所有的元素各执行
一次sizeof运算并将所得结果求和。注意,sizeof运算不会把数组转换成指针来处理
对string对象或vector对象执行sizeof运算只返回该类型固定部分大小,不会计算对象中的
元素占用了多少空间
因为执行sizeof运算能得到整个数组的大小,所以可以用数组的大小除以单个元素的大小得到数组中元素的个数
#endif
//sizeof(ia/sizeof(*ia)返回ia的元素个数
int number, *ia = &number;
constexpr size_t sz = sizeof(ia) / sizeof(*ia);
/*size_t是unsigned int类型,它被设计的足够大以便能表示内存中的任意对象
在cstddef头文件中定义了这个类型,这个文件是c标准库stddef.h头文件的C++语言版本*/
int arr2[sz];//正确:sizeof返回一个常量表达式,参见2.44节第58页
//练习题4.28输出每一种内置类型所占空间的大小
cout<<"------------"<<endl;
char str = 'a';
int iv1 = 0;
float fv2 = 0;
double dv3 = 0;
long double ldv4 = 0;
char *p1 = &str;
int *ip1 = &iv1;
float *fp2 = &fv2;
double *dp3 = &dv3;
long double *ldp4 = &ldv4;
cout<<"str size = "<<sizeof(str)<<endl;
cout<<"str *p1 = "<<sizeof(*p1)<<endl;
cout<<"str p1 = "<<sizeof(p1)<<endl;
cout<<"iv1 = "<<sizeof(iv1)<<endl;
cout << "*ip1 = " << sizeof(*ip1) << endl;
cout << "ip1 = " << sizeof(ip1) << endl;
cout << "fv2 = " << sizeof(fv2) << endl;
cout << "*fp2 = " << sizeof(*fp2) << endl;
cout << "fp2 = " << sizeof(fp2) << endl;
cout << "dv3 = " << sizeof(dv3) << endl;
cout << "*fp2 = " << sizeof(*dp3) << endl;
cout << "dp3 = " << sizeof(dp3) << endl;
cout << "ldv4 = " << sizeof(ldv4) << endl;
cout << "*ldp4 = " << sizeof(*ldp4) << endl;
cout << "ldp4 = " << sizeof(ldp4) << endl;
cout << "------------" << endl;
cout<<sizeof(bool)<<endl;
cout<<sizeof(char)<<endl;
cout<<sizeof(wchar_t)<<endl;
cout<<sizeof(char16_t)<<endl;
cout<<sizeof(char32_t)<<endl;
cout<<sizeof(short)<<endl;
cout<<sizeof(long)<<endl;
cout<<sizeof(long long)<<endl;
cout<<sizeof(double)<<endl;
cout<<sizeof(long double)<<endl;
//练习题4.29推断下面代码输出结果并说明理由。实际运算这段程序,结果与你想象一样吗,如果不同,为什么
int xx[10]; int*pp = xx;
cout<<sizeof(xx)/sizeof(*xx)<<endl;//数组有10个,每个占4个字节,就是40,除指针长度为4所以是40/4=10
cout<<sizeof(pp)/sizeof(*pp)<<endl;//x86系统指针是32位,x64是64位。32位下输出1,一个指针长度除以数值长度,两个都是4
//练习题3.30
#ifndef DECLARATION
(a)sizeof x + y (b) sizeof p->mem[i] (c) sizeof a < b (d)sizeof f()
改成(a)sizeof(x + y) (b) sizeof(p->mem[i]) (c) sizeof(a < b) (d)sizeof(f())
#endif
int x = 0, y = 0;
int i = 0;
cout << (((sizeof x) + y)==(sizeof x+y)) << endl;
//临时结构体
struct tmp
{
int mem[3] = { 1,2,3 };
};
tmp t, *p = &t;
cout<<((sizeof p->mem[i])==sizeof(p->mem[i]))<<endl;
int a = 0, b = 1;
cout<<(sizeof a<b == (sizeof a)<b)<<endl;
//声明临时函数
int f();
cout<<(sizeof f()==sizeof(f()))<<endl;
system("pause");
return 0;//
}
int f()//定义临时函数
{
int num = 0;
return num;
}
=====================================================================
第4章 表达式 141页 类型转换
=====================================================================
//QQ108201645编写
#include <iostream>
#include <string>
using namespace std;//使用命名空间
#ifndef DECLARATION
#define DECLARATION
//说明部分
//执行已定义声明的话将不再执行
#endif
int main()
{
int ivalue = 3.541 + 3;//编译器可能会警告损失了精度
#ifndef DECLARATION
隐式转换
在大多数表达式中,比int类型小的整型值首先提升较大的整数类型
在条件中,非布尔值转换成布尔类型
初始化过程中,初始值转换成变量的类型:在赋值语句中,右侧运算对象转换成左侧运算对象的类型
如果算术运算或关系运算对象有多种类型,需要转换成同一种类型
如第6章将要介绍的,函数调用时也会发生类型转换
#endif
;
//调整并初始化一下,为了通过编译
bool flag=0; char cval='a';
short sval=0; unsigned short usval=0;
int ival=0; unsigned int uival=0;
long lval=0; unsigned long ulval=0;
float fval=0; double dval=0;
3.14159L + 'a';//'a'提升成int ,然后该int值转换成long double
dval += ival;//ival转换成double
dval += fval;//fval转换成double
ival += dval;//dval(丢弃小数部分后)转换成int
flag = dval;//如果dval是0,则flag是false,否则flag是true
cval += fval;//如cval提升成int ,然后该int值转成float
sval += cval;//sval和cval都提升成int
cval += lval;//cval转换成long
ival += ulval;//ival转换成unsigned long
usval += ival;//根据unsigned short和int所占的空间大小进行提升
uival += ival;//根据unsigned int和long所占空间大小进行转换
#ifndef DECLARATION
在第一个加法中,小写字母'a'是char类型的常量,它其实能表示一个数字值(参
见2.1.1节,第30页)。到底这个值是多少完全依赖机器的字符集,在我们的环境
中,'a'对应的数字值是97。当把'a'和一个long double类型相加时,char类型的值
首先提升成int类型,然后int类型的值再转换成long double 类型。最终我们把这个转换
后的值与那个字面值相加。最后的两个含有无符号类型值的表达式也比较
有趣,它们的结果依赖机器
#endif
if (fval) { ; }//判定fval的值是0还是非0,
dval = fval + ival;//把int转成float与fval相加转换double类型后并赋值给double
dval = ival * cval;//把cval转换成int与ival相加再转double后赋值给dval
system("pause");
return 0;//
}
=====================================================================
第4章 表达式 143页 类型转换(习题4.35)
=====================================================================
//QQ108201645编写
#include <iostream>
#include <string>
using namespace std;//使用命名空间
#ifndef DECLARATION
#define DECLARATION
//说明部分
//执行已定义声明的话将不再执行
#endif
char* get_string()
{
char sval[] = "abc";
return sval;
}
int main()
{
//练习题4.35假设有如下定义
char cval='a'; int ival=0; unsigned int ui=0;
float fval=0; double dval=0;
cval = 'a' + 3;//'a'转换为int,计算完后int转换为char,赋值给char类型
fval = ui - ival * 1.0;//ival转换为double,ui转换为double,计算完后double转换为float赋值给fval
dval = ui * fval;//ui转换为float,计算完后float转换为double类型后赋值给dval
cval = ival + fval + dval;//ival转换为float,计算完后float转换为double,最后计算完后double转换为char类型后赋值给cval
//其它隐式转换
int ia[10];//含有10个整数的数组
int *ip = ia;//ia转换成指向数组首元素的指针
//C++还规定几种其它指针转换方式,包括常量整数值0或者字面值nullptr能转换成任意指针类型
//指向任意非常量的指针能转换成void* 指向任意对象的指针能转换成const void*。
//(15.2.2节(第530页)介绍有继承的类型指针还有另外一种转换
const char* cp = get_string();
if (cp) {
;
}/*...*/ //如果指针不是0,条件为真
while (*cp) { ; }//如果*cp不是空字符,条件为真
//转换成常量
//允许将指向非常量类型的指针转换成指向相应的常量类型的指针,对于引用也是这样。也就是说,如果
//T是一种类型,我们就能将T的指针或引用分别转换成指向const T的指针或引用(参见2.41,第54页或2.42节,第56页)
int i;
const int &j = i;//非常量转换成const int 的引用
const int*p = &i;//非常量的地址转换成const的地址
//int& r = j, *q = p;//错误不允许const转换成非常量
//相反的转换并不存在,因为它试图删除掉底层const
//类类型能定义由编译器自动执行的转换,不过编译器每次只能执行一种类类型的转换
//在(7.5.4节第263页)中我们将看到一个例子,如果同时出现多个转换请求,这些请求将被拒绝
string s, t = "a value";//字符串面值转换成string类型
while (cin >> s) { ; }//while的条件部分把cin转换成布尔值
//方法只能按ctrl+z回车结束
system("pause");
return 0;//
}
=====================================================================
第4章 表达式 143页 显式转换
=====================================================================
#include <iostream>
#include <string>
using namespace std;//使用命名空间
#ifndef DECLARATION
#define DECLARATION
//说明部分
//执行已定义声明的话将不再执行
#endif
int main()
{
int i=2, j=1;
#ifndef DECLARATION
double slope = i / j;//使用某种方法将i和/或j显示的转换成double,这种方法称作 强制类型转换
//显然有时不得不使用强制类型转换,但这方法本质上是非常危险的
//命名的强制类型转换具有如下类型
ast - name<type>(expression);
其中,type是转换的目标类型,而expression是要转换的值。如果type是引用类型,则结果
是左值。cast - name是static_cast、dynamic_cast、const_cast和reinterpret_csat中的一种
dynamic_cast支持运行时类型识别,我们将在19.2节第730页对其做更详细的介绍。cast - name
指定了执行的是哪种转换
static_cast
任何具有明确定义的类型转换,只要不饮食底层const,都可以使用static_cast
例如,通过一个运算对象强制转换成double类型就能使其表达式执行浮点数除法
#endif // DECLARATION
//进行强制类型转换以便执行浮点数除法
;
double slope = static_cast<double>(j) / i;
//当需要把一个较大的算术类型赋值给较小的类型时,static_cast非常有用,但会损失精度,如果没有显示转换,编译器会提示警告
//static_cast对于编译器无法自动执行的类型转换也非常有用,例如,我们可以使用static_cast找回存在于void*指针
//中(第2.3.2节第50页中)中的值
double d = 0;
void *p = &d;//正确,任何非常量都能存入void*
//正确:将void*转换回初始的指针类型
double* dp = static_cast<double*>(p);
//当我们把指针存放在void*中并用static_cast将其强制转换原来的类型时应该确保指针的值不变
//也就是说,强制类型的结果将与原始的地址值相等保证转换所得的类型就是指针所指的类型,类型一旦
//不符,将产生未定义的后果
#ifndef DECLARATION
const_cast
只能改变运算对象的底层const(参见2.4.3, 第57页)
#endif
;
const char *pc = 0;
char* p1 = const_cast<char*>(pc);//正确:但是通过p1写值是未定义的行为
//将常量转换成非常量称为去掉const性质,一旦去掉,编译器就不再阻止我们对该对象进行写操作了,如果对象本身
//不是一个常量,使用强制类型转换获得写权限是合法的行为。而如果对象是一个常量,再使用const_cast执行写操作
//就会产生未定义的后果
//只有const_cast能改变表达式的常量属性,使用其它形式的命名强制类型转换改变表达式的常量属性都将引发编译器的错误
//同样,也不能用const_cast改变表达式的类型
const char*cp=nullptr;
//char* p = static_cast<char*>(cp);//错误,static_cast不能转换掉const性质
static_cast<string>(cp);//正确:字符串字面值转换成string类型
//const_cast<string>(cp);//错误:const_cast只能改变常量属性
#ifndef DECLARATION
reinterpret_cast
通常为运算对象的位模式提供较低层次上的重新解释。
#endif
;
int *ip=nullptr;//等价于int* i=0
char* pc1 = reinterpret_cast<char*>(ip);
#ifndef DECLARATION
我们必须牢记pc所指的真实对象是一个int而非字符,如果把pc当成普通的字符指针
使用就可能在行时发生错误。例如
string str(pc1);
可能导致异常的运行时行为
使用reinterpret_cast是非常危险的,用pc初始化str的例子很好的证明了这一点。
其中关键问题的类型改变了,但编译器没有给出任何警告或者错误的提示信息。
当我们用int的地址初始化pc时,由于显示的声称这种转换合法,所以编译器不会发出
任何警告或错误信息。接下来再使用pc1时就会认定它的值是char*类型,编译器没法知道它实
际存放的是指向int的指针。最终的结果就是,在上面的例子中虽然用了pc1初始化str没什么实际
意义,甚至还可能引发更糟糕的后果,查找这类问题的原因较困难,如果将ip强制转换成pc1的语句
和用pc初始化string对象的语句分属不同文件就更是如此了,强烈建议避免使用强制类型转换reinterpret_cast
旧式的强制转换
type(expr);//函数形式的强制类型转换
(type)expr;//C语言风格的强制类型转换
#endif
//练习题4.36假设ival是int类型,d是double类型,书写表达式ival*=d使其执行整数类型的乘法而非浮点数的乘法
int ival = 5;
double dval = 2.3;
ival *= static_cast<int>(dval);
system("pause");
return 0;//
}
=====================================================================
#include <iostream>
#include <string>
using namespace std;//使用命名空间
#ifndef DECLARATION
#define DECLARATION
//说明部分
//执行已定义声明的话将不再执行
#endif
int main()
{
//练习题4.37用命名的强制类型转换改写下列旧式的转换语句
int i = 0;
double d= 0;
const string *ps = 0;
char *pc= 0;
void *pv;
#ifndef DECLARATION
pv = (void*)ps; i = int(*pc);
pv = &d; pc = (char*)pv;
#endif
pv = const_cast<void *>(static_cast<const void*>(ps));//先转static_cast再去掉常量性转换void*类型
i= static_cast<int>(*pc);//把*pc的值转int
pv = static_cast<void*>(&d);
pc = static_cast<char*>(pv);
//练习4.38:说明下面这条表达式的含义
double slope = static_cast<double>(j / i);//把j除i的结果强制转换成double类型赋值给slope
system("pause");
return 0;//
}
=====================================================================
表4.4:运算符优先级 | ||||
结合律与运算符 | 功能 | 用法 | 参考页码 | |
左 | :: | 全局作用域 | ::name | 256 |
左 | :: | 类作用域 | class::name | 79 |
左 | :: | 命名空间作用域 | namespace::name | 74 |
左 | . | 成员选择 | object.member | 20 |
左 | -> | 成员选择 | pointer->member | 98 |
左 | [] | 下标 | expr[expr] | 104 |
左 | () | 函数调用 | name(expr_list) | 20 |
左 | () | 类型构造 | type(expr_list) | 145 |
右 | ++ | 后置递增运算 | lvalue++ | 131 |
右 | -- | 后置递减运算 | lvalue-- | 131 |
右 | typeid | 类型ID | typeid(type) | 731 |
右 | typeid | 运行时类型ID | typeid(expr) | 731 |
右 | explicit cast | 类型转换 | cast_name<type>(expr) | 144 |
右 | ++ | 前置递增运算 | ++lvalue | 131 |
右 | -- | 前置递减运算 | --lvalue | 131 |
右 | ~ | 位求反 | expr | 136 |
右 | ! | 逻辑非 | ! expr | 126 |
右 | - | 一元负号 | - expr | 124 |
右 | + | 一元正号 | + expr | 124 |
右 | * | 解引用 | * expr | 48 |
右 | & | 取地址 | &lvalue | 47 |
右 | () | 类型转换 | (type)expr | 145 |
右 | sizeof | 对象的大小 | sizeof expr | 139 |
右 | sizeof | 类型的大小 | sizeof(type) | 139 |
右 | Sizeof… | 参数包的大小 | sizeof…(name) | 619 |
右 | new | 创建对象 | new type | 407 |
右 | new[] | 创建数组 | new type[size] | 407 |
右 | delete | 释放对象 | delete expr | 409 |
右 | delete[] | 释放数组 | delete[] expr | 409 |
右 | noexcept | 能否抛出异常 | noexcept (expr) | 690 |
左 | ->* | 指向成员选择的指针 | ptr->*ptr_to_member | 740 |
左 | .* | 指向成员选择的指针 | obj.*ptr_to_member | 740 |
左 | * | 乘法 | expr * expr | 124 |
左 | / | 除法 | expr / expr | 124 |
左 | % | 取模(取余) | expr % expr | 124 |
左 | + | 加法 | expr + expr | 124 |
左 | - | 减法 | expr - expr | 124 |
左 | << | 向左移位 | expr << expr | 136 |
左 | >> | 向右移位 | expr >> expr | 136 |
左 | < | 小于 | expr < expr | 126 |
左 | <= | 小于等于 | expr <= expr | 126 |
左 | > | 大于 | expr > expr | 126 |
左 | >= | 大于等于 | expr >= expr | 126 |
左 | == | 相等 | expr == expr | 126 |
左 | != | 不相等 | expr != expr | 126 |
左 | & | 位与 | expr & expr | 136 |
左 | ^ | 位异或 | expr ^ expr | 136 |
左 | | | 位或 | expr | expr | 136 |
左 | && | 逻辑与 | expr && expr | 126 |
左 | || | 逻辑或 | expr || expr | 126 |
右 | ?: | 条件运算符 | expr ? expr : expr | 134 |
右 | = | 赋值 | lvalue = expr | 129 |
右 | *=, /=, %= | 复合赋值 | lvalue += expr等 | 129 |
右 | +=, -= | 129 | ||
右 | <<=, >>= | 129 | ||
右 | &=, |=, ^= | 129 | ||
右 | throw | 抛出异常 | throw expr | 173 |
左 | , | 逗号 | expr, expr | 140 |