C++ 的那些坑 (Day 0)
C++ 的那些坑 (Day 0)
永远的for循环
其实这里要说的并不是for循环本身还是其中的计数变量的类型的选择。
std::string s = "abcd"
for (string::size_type i=0; i<s.size(); i++) {
std::cout<<s[i]<<" ";
}
// a b c d
对于以上代码估计很多人是不会这样装逼的使用size_t
或者string::size_type
类型的,而是直接使用int类型,虽然string.size()以及其他一些容器如vector的size()方法返回的都是size_t类型的值。再来看下面这段代码
std::string s = "abcd"
for (string::size_type i=0; i<s.size() - 10; i++) {
std::cout<<s[i]<<" ";
}
功能上来说这个for循环是负责输出字符串中除去最后10个字符的前面部分。当运行编译运行时,会发现程序胡乱输出,并最终可能给出一个Segmentation fault
。这个非常出乎意料,按照原先的剧情,这个for应该根本无法进入才对。当你使用debug工具去debug时你会惊奇的发现真的会进入循环内部。
原因在于
size_t
或者string::size_type
其实是一个unsigned
的整数,因此情景中s.size() - 10
的结果值并不是一个负数,而是一个非常大的整数。
再来看另一个场景,这次是逆序输出字符串中的字符:
std::string s = "abcd"
for (size_t i=s.size() - 1; i >= 0; i--) {
std::cout<<s[i]<<" ";
}
此时我们还是装逼的使用了size_t
类型作为迭代变量,编译运行会发现程序又是一阵抽风或者根本停不下来。原因还是一样的,size_t
作为无符号类型无论如何在i >= 0
这个条件判断中总是会通过的。其实编译器在编译时已经给出了警告:
sizet.cpp:6:31: warning: comparison of unsigned expression >= 0 is always true [-Wtautological-compare]
for (size_t i=s.size() - 1; i>=0; i--) {
~^ ~
1 warning generated.
所以一般情况下我们还是直接使用int作为迭代变量或者作为终止条件变量:
int end = s.size() - 10;
for (int i=0; i<end; i++) {
std::cout<<s[i]<<" ";
}
当然这样会有编译警告说类型转换可能会有精度丢失。但是我们要想一下有符号的int能表示2GB的空间,也就是大约20亿的数据项,如果这个数据项是整数的话在多于8GB的空间时才会溢出。对于一般的应用程序级别的容器根本无法达到这种量级。
当然的,对于广大装逼爱好者,还是要掌握容器迭代的正确姿势的:
// C++11 foreach (string s = "abcd")
for (char ch : s) {
std::cout<<ch<<" ";
}
// using iterator
for (auto iter = s.begin(); iter != s.end(); i++) {
}
// using reverse iterator
for (auto iter = s.rbegin(); iter != s.rend(); i++) {
}
有偏见的引用
下面是一个再寻常不过的例子
int value = 10086;
int &valref = value;
当我们稍作修改改为如下语句时,编译就无法通过
double value = 10086.233;
int &valref = value;
type.cpp:8:7: error: non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'double'
int &valref = value;
^ ~~~~~
1 error generated.
看来引用必须是同类型的,然而看了下面这段又不能如此过早下结论
double value = 10086.233;
const int &valref = value;
cout<<valref<<endl;
上述代码会编译通过并输出10086。C++ Primer 5th
给出的解释如下:
double value = 10086.233;
const int tmp = value;
const int &valref = tmp;
在使用常量引用时因为只需要读取原变量的值,所以中间创建了一个用于隐式类型转换的临时变量,常量引用最终使用的值就是这个临时变量的值。那么这里就有个问题,下面两段代码的输出会是什么:
int value = 10086;
const &valref = value;
value = value + 1;
cout<<valref<<endl;
输出:10087
double value = 10086.233;
const &valref = value;
value = value + 1;
cout<<valref<<endl;
输出:10086
得知这个消息我还是稍稍有点震惊的,不过一想这种代码估计除了这里其他地方就不会出现吧!类型隐式转换还是挺危险的。上述的都是基本类型的变量,下面来试一试复杂类型看看有没有真的产生临时变量:
#include <iostream>
#include <string>
using namespace std;
class Double {
private:
double val;
public:
Double(double v = 0.0) : val(v) {
cout<<"Double constructor with double value"<<endl;
}
double value() const {
return val;
}
void change(double delta) {
val += delta;
}
};
class Integer {
private:
int val;
public:
Integer(int v = 0) : val(v) {
cout<<"Integer constructor with integer value"<<endl;
}
Integer(Double& v) : val(v.value()) {
cout<<"Integer constructor with Double"<<endl;
}
int value() const {
return val;
}
};
int main() {
Double dnum(10086.233);
const Integer &inum = dnum;
dnum.change(1);
cout<<inum.value()<<endl;
return 0;
}
由于Integer类的构造函数中可以接受Double对象并且没有声明为explicit所以可以将Double类型隐式转换为Integer类型。当使用复杂类型再来看这个规则时,会更明白的发现为什么中间要产生一个用于类型转换的临时对象。因为类型不同使用不同的引用类型必然引起混乱,就像一个类型的指针指向不同的类型的存储区域一样(引用在汇编就是用的指针,机器并没有引用对应的专门指令)。而临时对象的产生使得引用变量和最初类型的变量脱离了关系(如果是基本类型,或者用于类型转换的构造函数中没有对内部结构进行共享)。
这也是为什么const引用可以指向不同类型而一般引用不行,因为const引用只能读取对象数据,信息是单向流动(类型转换生成临时对象即可解决),而非const引用还可以修改原对象数据,是一个双向的过程,仅仅靠类型转换生成临时对象是无法解决的。其实觉得能够实现const引用不同类型已经是非常不错了,也没比较在推广下去,因为这种几乎钻牛角尖的语言特性反而使得C++变得难用易错。引用最一般的解释就是说是对象的别名,现在因为不同类型的转换而使得与原有对象可能脱离了联系,也就没有了变量“别名”的语义。
C++ 的那些坑 (Day 0)的更多相关文章
- JavaScript 跳坑指南
JavaScript 跳坑指南 坑0-String replace string的replace方法我们经常用,替换string中的某些字符,语法像这样子 string.replace(subStr/ ...
- jquery 的 each 方法中 return 的坑
jquery 的 each 方法中 return 的坑 Chapter 0 在项目中使用 jquery 的 each 方法时想在 each 的循环中返回一个布尔类型的值于是掉进一个坑中... Chap ...
- Android SDK4/5/6/7,相册、拍照及裁剪功能及遇见的坑
保存照片和视频到系统相册显示- http://blog.csdn.net/chendong_/article/details/52290329 Android 7.0 之拍照与图片裁剪适配-http: ...
- 动归专题QAQ(两天创造的刷题记录哟!✿✿ヽ(°▽°)ノ✿✿)(未填坑)
1092 采药:由于没有限制开始时间和结束时间,01背包就好了 1095 开心的金明:01背包,无fuck说 1104 摆花:f[i][j]表示摆了i种花,第i种花摆了j种的方案数,乱转移0.0(感觉 ...
- jumpserver遇到的坑
安装:https://github.com/jumpserver/jumpserver,看readme照着做就行,下面是遇到的坑. 0.4.4版坑: 1.要升级pip,否则有的包装不上 2.p ...
- HBase2.0新特性解析
作者 | 个推大数据运维工程师 行者 升级背景 个推作为专业的数据智能服务商,在业务开展过程中存在海量的数据存储与查询的需求,为此个推选用了高可靠.高性能.面向列.可伸缩的分布式数据存储系统--HBa ...
- 常用算法——排序(一)
排序(Sort)是计算机程序设计中的一种重要操作,也是日常生活中经常遇到的问题.例如,字典中的单词是以字母的顺序排列,否则,使用起来非常困难.同样,存储在计算机中的数据的次序,对于处理这些数据的算法的 ...
- Noip2016 总结&反思
一直在期盼的联赛,真正来临时,却远不像我想象的样子. 有些事,真的不敢再想. 算法可以离线,时光却不能倒流.dfs可以回溯,现实却没有如果. 有些事,注定只能成为缺憾,抱恨终生. 不得不说今年Noip ...
- swift-计算器(斯坦福公开课)
看了斯坦福老头的课,真心觉得,我的中文怎么也变的这么垃圾了.是关于iOS8的课程,用swift写的,一个计算器应用的制作,看看人家的课,再看看咱们学校的课(不过垃圾学校,纯粹觉得大学浪费了),废话啊, ...
- 群赛 ZOJ3741(dp) ZOJ3911(线段树)
zoj3741 简单dp.wa了两个小时,中间改了好多细节.后来还是不对,参考了别人的代码,发现一个致命问题,初始化的时候,不是每种状态都能直接达到的.初始化成-1. (题目有个小坑,0<=L& ...
随机推荐
- 一次对路边饮用水RFID供应机的跑路玩法
"如何成为一个合格的硬件白帽子? 答案: 一个有聪明大脑的你 要有归纳类比和善于用GOOGLE的能力(百度?放弃吧) 善于翻阅和查询相关开发文档和强大的跑路能力." WARNING ...
- [Vuejs] 组件 v-if 和 v-show 切换时生命周期钩子的执行
v-if 初始渲染 初始值为 false 组件不会渲染,生命周期钩子不会执行,v-if 的渲染是惰性的. 初始值为 true 时,组件会进行渲染,并依次执行 beforeCreate,created, ...
- C#6.0语言规范(十五) 委托
委托启用其他语言(如C ++,Pascal和Modula)已使用函数指针进行寻址的方案.但是,与C ++函数指针不同,委托是完全面向对象的,与成员函数的C ++指针不同,委托封装了对象实例和方法. 委 ...
- 机器学习-Matplotlib绘图(柱状图,曲线图,点图)
matplotlib 作为机器学习三大剑客之一 ,比热按时无比强大的 matplotlib是绘图库,所以呢我就分享一下简单的绘图方式 #柱状图 #导报 柱状图 import matplotlib. ...
- cookie和session的区别,分布式环境怎么保存用户状态
cookie和session的区别,分布式环境怎么保存用户状态 1.cookie数据存放在客户的浏览器上,session数据放在服务器上. 2.cookie不是很安全,别人可以分析存放在本地的COOK ...
- Selenium3 + Python3自动化测试系列五——常用断言Assertion
断言Assertion 验证应用程序的状态是否同所期望的一致. 常见的断言包括:验证页面内容,如标题是否为X或当前位置是否正确,或是验证该复选框是否被勾选. selenium 提供了三种模式的断言:a ...
- centOS7.10 KDE桌面字体设置推荐
安装完centOS7.10的KDE桌面后,第一次使用觉得字体太难看了,特别是终端,看着很难受,调整多次后觉得如下设置舒服很多,分享出来以供参考. 其中等宽字 这样整体看着就会舒服很多 ******** ...
- zookeeper+kafka集群的安装部署
准备工作 上传 zookeeper-3.4.6.tar.gz.scala-2.11.4.tgz.kafka_2.9.2-0.8.1.1.tgz.slf4j-1.7.6.zip 至/usr/local目 ...
- 用AOP思想改造一个服务器的数据存储
背景是有一个游戏服务器一直以来都是写SQL的, 后来改过一段时间的redis, 用的是别的员工写的类orm方式将实体类型映射成各种key-value对进行写入, 但是仍有一个缺点就是需要在增\删\改的 ...
- Metasploit数据库问题汇总
数据库在metaspoit中是相当重要的,当做一个大型渗透测试项目的时候,收集到的信息是相当大的,当和你的同伴一起协同作战的时候,你们可能 在不同的地方,所以数据共享很重要了!而且Metasploit ...