博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
读书笔记_代码大全_第10章_使用变量的一般事项
阅读量:5168 次
发布时间:2019-06-13

本文共 4657 字,大约阅读时间需要 15 分钟。

使用变量的一般事项

注:希望我的读书笔记能带你翻过20页的书 

本章主要讨论变量的一些使用事项,看似非常基础,但你是否真有“好的使用习惯”?不妨来看看。

1. 在声明变量的时候就应该初始化

这告诉我们应该把int count换成int count = 0,把short *pointer换成short *pointer = 0。有些语言,比如VB不支持声明的时候就初始化,那就在变量声明的下一句就给赶紧给它赋个值吧!当变量是对象时,则要确保这个对象被合理地初始化了,在类中要定义构造函数(最好有个默认的构造函数)。

 

2. 能使用const的地方,就尽量用const

(1)     定名常量用const

一些常量,比如一年中月份的个数,可以这样定义:const int MONTH_AMOUNT = 12,这就是定义定名常量的方法,它比#define MONTH_AMOUNT 12 要好,因为用定名常量的方法可以让编译器再把把关,比如const int MONTH_AMOUNT = 12.0时编译器会给出警告,而宏定义则失去了编译器的帮助。

 

(2)     函数中包含引用的参数,也尽量使用const

比如两数相加的函数int add(int& firstNumber, int& secondNumber),如果调用函数是add(3, 5)就会报编译错,而int add(const int& firstNumber, const int& secondNumber)则不会这样,因为非const引用参数必须要附着到与之类型完全匹配而且也要是非const的变量上,而const引用参数的约束则变弱,既可以附着同类型的非const变量,也要以附着同类型的const变量,甚至包括常量。

 

(3)     指针const

指针的const分成两种,即指向常量的指针和常量指针,这可是出笔试题喜欢问到的地方。指向常量的指针要求指针指向的值不改变,比如const int* p = &a,这之后再有*p = 5就会报错,因为不允许改变指针所指向的值。常量指针要指针本身指向不能改变,比如int* const p = &a,这之后再有p = &b,就会报错,因为指针的指向不能变化了。还有这两种情况的混合体,比如const int* const p = &a,这时就要求指针指向以及指向的值同时都不能改变,称为指向常量的常量指针,比较绕口,不过理解意思就行了。这里还有一个比较tricky的地方,好像是今年中兴的考师,int const *p是上面的哪种情况?哈哈,是不是绕晕了,这其实是指向常量的指针。方法是看const与谁离的近,当表示指向常量的指针时,const后面出现的是*p(表示值为常量),而当表示常量指针时,const后面出现的是p(表示指针本身是常量),也就是看*与const的相对位置关系。

 

(4)     当类的成员函数不改变类成员变量的,也尽量在其后放置const

比如

1 class A2 {3 private:4 int a;5 public:6 void print() const;7 };

 

当print只是去打印而不改变类的成员变量时,就可以在其后放置const,防止误操作修改了成员变量。

总而言之,const防止变量(包括指针变量)被修改,同时也会降低对使用的约束要求。

 

3. 尽量使用小的变量作用域

变量的作用域是指变量有效的范围,由小到大依次分为块作用域,函数作用域,类作用域和全局作用域。

 

块作用域比如下面的i,它只存在于for循环内,比如:

1 for(int i = 0; i < 10; ++i)2 {…}3 i = 3; // 这句话编译器报错,因为这时候编译器已经不认识i了。

 

 

函作用域是指变量只在函数范围内有效,比如:

1 void fun(int a, int b) 2 { 3 int c = 3; 4 … 5 } 6 int main() 7 { 8 … 9 fun(5, 6);10 a = 3; // 编译器报错11 c = 5; // 编译器报错,因为这时候编译器已经不认识a和c了。12 }

 

 

类作用域是指变量在整个类范围内都是有效的,比如:

1 class A 2 { 3     int a; 4 public: 5     void setA(int t) 6     { 7          a = t; // 有效,类作用域 8     } 9     int getA()10     {11      return a; // 有效,类作用域12     }13 }

 

但在类外a就不能直接引用了。

 

全局作用域是最广的,它在自它声明位置起,到本文件结束,都可以使用它。比如:

1 int a = 3; 2  3 int main() 4 { 5          cout << a << endl; // 有效 6 } 7  8   9 void fun()10 {11          int b = a + 3; // 有效,b = 612 }

 

注意这里使用全局的a都是OK的,但若局部再定义相同的a,比如把fun()改成:

1 void fun()2 {3          int a = 5;4          int b = a + 3;// 有效,b = 85 }

 

可以看到,局部变量会屏蔽掉全局变量。另外,全局数据尽量不要使用,因为这不是线程安全的(无法承受多个线程对之读写),也会破坏程序的模块性,不利于封装和重构。

本书说到一个很重要的知识点,就是作用域提升的问题,存在这样一种现象,那就是将小作用域提升至大作用域时,需要改动的地方会很少(比如将块作用域提升至函数作用域,只要把变量i拿到for循环的外面就行了),但反过来,将大作用域减少至小作用域时,需要改动的地方就会很多,还是用这个例子,假定i在for循环外面定义了:

1 int i = 0;2 3 for(i = 0; i < 10; ++i)4 {…}5 6 i = 3;

 

这时候看似再把i放到for循环里面就OK了,但这样做编译器会报错,因为for的下面还有对i的使用,要把下面用到i的地方统统修改,可见会很麻烦!

大型的程序常常需要修改,变量作用域时有更新,因此在初次编写的时候,尽量用小的作用域!这样扩展至大作用域时会轻松很多。

 

4. 尽量使变量的生存时间减小

变量的生存时间是指其有效期,比如对于for循环块:

1 for(int i =0; i < 3; ++i){…}

变量i只在for循环内生存,一旦出了for循环i就不能使用了(现在VS都是这样处理的,但老版本的VC6.0却认为i的生存时间自此开始,直到程序尾)。

又如函数内

1 void fun(){
int a = 3;…}

 变量a只在函数内生存,一旦出了函数,a的“生命”就结束了,也就不能使用了。

相信读者读到这里,心里肯定有疑惑,变量生存时间不是与作用域差不多嘛!确实,很多情况下(比如前面两个例子),变量的生存时间与变量的作用域刚好重合,但两者其实不是等价的,两者是这样一种关系:变量的生存时间≥变量的作用域。我再举个例子,比如有这样一个函数:

1 void f()2 {3          static int s = 3;4          …5 }

 

现在问你,s的作用域是什么,生存时间又是什么?作用域你一般不会答错,就是这个函数内,在函数外使用s会使编译器报错;但s的生存时间不是你想像的,s生存时间很长,从程序开始时就在了,因为在静态变量与一般的变量是分开存储的,静态变量和全局变量存在于内存空间的全局数据区,这里面的数据是不会随着f()的结束而消亡的——它们一直都在,直到这个程序终止。又如:

1 void fp()2 {3          int *p = new int(3);4 }

 

p在函数结束后的生命就结束了,作用域也限定在fp()函数内,但p所指向的一个字节的内存空间(这一个字节存的是整型的3)却一直存在,直到程序运行结束后被操作系统回收。为什么会这样呢?因为new是在堆上分配的空间,除非对其delete,堆上的内存是不会随着子函数的消亡而回收的。那为什么p会消亡?因为p只是对这一块内存的引用,它生存在栈上。事实上,除静态变量以外的局部变量的都位于内存空间中的栈上,函数调用的开始和结束会伴随着一系列的栈变量弹出与栈变量压入,局部变量的这一系列的操作中会诞生和消亡。

管理代码时最头疼的问题是一下子要关注很多“还活着”的变量,所以说“尽量使变量的生存时间减小”,这样我们都会用更集中的精力来对付更重要的变量。因此,个人觉得C++在这个方面做的比C要好,C要求在函数的开头就要给出变量的声明,在后面才能使用,但往往自其声明到使用,会相隔很长很长,它出生的太早了!

上面说了那么多,我其实只想表达“变量的作用域尽量短,生存时间也尽量短”,本书提供了以下四种方法:

(1)     在循环开始之前再去初始化该循环里使用的变量,而不是在该循环所属的子程序的开始处初始化这些变量;

(2)     把相关语句放在一起;

(3)     把相关语句提取成单独的子程序;

(4)     开始时采用最短的作用域与生存时间,然后根据需要扩展。

 

5. 选择适合的绑定时间

学过多态的同学知道“绑定”这个词,有“早绑定”与“迟绑定”之分,早绑定发生在编译的时候,而迟绑定则发生在运行的时候。

像int a = 12或者int a = MONTH_AMOUNT(其中MONTH_AMOUNT是之前定义的具名常量),就是在编译期绑定好的,即将12这个数值与a绑定在一起。而int a = getMonthAmount()则发生在运行时,只有到程序执行到这一句时,才知道返回值是什么,所以a只有在这个时候才与函数的返回值发生绑定。早绑定简单但机械,而迟绑定复杂却灵活,比如多态就可以根据不同的对象采取作出不同的行为。到底使用什么样的绑定方式,则根据你的需要。但有一点,int a = MONTH_AMOUNT显然要优于int a = 12,12在这里是magic number(莫名其妙冒出来的值),但用MONTH_AMOUNT去代替12无疑增加了可读性,也便于修改。

 

6. 为变量指定单一用途

一句话,一个变量就做一件事,比如pageCount表示已经打印的纸的数量,但有的程序员会了节省变量,用pageCount = -1表示打印时出错,这个从原理上来说行的通,因为pageCount的只可能取大于等于0的正整数,-1在这里相当于哨兵了,表征打印出错。但这样做其实不好,完全可以用printStatus来单独作哨兵。最好还是让pageCount做它本来该做的事——计算打印纸数量。

 

最后总结一下这几个要点:

(1)     变量声明的时候就初始化,若是对象,则提供一个至少含有默认构造函数的类;

(2)     能用const的地方尽量用const;

(3)     应使变量作用域尽量窄,生存时间尽量短;

(4)     选择合适的变量与值的绑定方式;

(5)     把每个变量用于唯一的用途。

<end>

转载于:https://www.cnblogs.com/jerry19880126/archive/2012/12/22/2829394.html

你可能感兴趣的文章
转:Linux设备树(Device Tree)机制
查看>>
iOS 组件化
查看>>
(转)Tomcat 8 安装和配置、优化
查看>>
(转)Linxu磁盘体系知识介绍及磁盘介绍
查看>>
tkinter布局
查看>>
命令ord
查看>>
Sharepoint 2013搜索服务配置总结(实战)
查看>>
博客盈利请先考虑这七点
查看>>
使用 XMLBeans 进行编程
查看>>
写接口请求类型为get或post的时,参数定义的几种方式,如何用注解(原创)--雷锋...
查看>>
【OpenJ_Bailian - 2287】Tian Ji -- The Horse Racing (贪心)
查看>>
Java网络编程--socket服务器端与客户端讲解
查看>>
List_统计输入数值的各种值
查看>>
学习笔记-KMP算法
查看>>
Timer-triggered memory-to-memory DMA transfer demonstrator
查看>>
跨域问题整理
查看>>
[Linux]文件浏览
查看>>
64位主机64位oracle下装32位客户端ODAC(NFPACS版)
查看>>
获取国内随机IP的函数
查看>>
今天第一次写博客
查看>>