Cplusplus topic

2024-02-17 15:18
文章标签 topic cplusplus

本文主要是介绍Cplusplus topic,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

条款1:尽量用const和inline而不用#define


这个条款最好称为:“尽量用编译器而不用预处理”,因为#define经常被认为好象不是语言本身的一部分。这是问题之一。再看下面的语句:

 

#define ASPECT_RATIO 1.653

编译器会永远也看不到ASPECT_RATIO这个符号名,因为在源码进入编译器之前,它会被预处理程序去掉,于是ASPECT_RATIO不会加入到符号列表中。如果涉及到这个常量的代码在编译时报错,就会很令人费解,因为报错信息指的是1.653,而不是ASPECT_RATIO。如果ASPECT_RATIO不是在你自己写的头文件中定义的,你就会奇怪1.653是从哪里来的,甚至会花时间跟踪下去。这个问题也会出现在符号调试器中,因为同样地,你所写的符号名不会出现在符号列表中。
解决这个问题的方案很简单:不用预处理宏,定义一个常量:

 

const double ASPECT_RATIO = 1.653;

这种方法很有效。但有两个特殊情况要注意。
首先,定义指针常量时会有点不同。因为常量定义一般是放在头文件中(许多源文件会包含它),除了指针所指的类型要定义成const外,重要的是指针也经常要定义成const。例如,要在头文件中定义一个基于char*的字符串常量,你要写两次const:

 

const char * const authorName = "Scott Meyers";

关于const的含义和用法,特别是和指针相关联的问题,参见 条款21。

 

另外,定义某个类(class)的常量一般也很方便,只有一点点不同。要把常量限制在类中,首先要使它成为类的成员;为了保证常量最多只有一份拷贝,还要把它定义为静态成员:
   

class GamePlayer {
private:
static const int NUM_TURNS = 5; // constant eclaration 
int scores[NUM_TURNS];		// use of constant
...
};

还有一点,正如你看到的,上面的语句是NUM_TURNS的声明,而不是定义,所以你还必须在类的实现代码文件中定义类的静态成员:

 

const int GamePlayer::NUM_TURNS;	// mandatory definition;
// goes in class impl.file

 

你不必过于担心这种小事。如果你忘了定义,链接器会提醒你。

旧一点的编译器会不接受这种语法,因为它认为类的静态成员在声明时定义初始值是非法的;而且,类内只允许初始化整数类型(如:int, bool, char 等),还只能是常量。
在上面的语法不能使用的情况下,可以在定义时赋初值:

 

class EngineeringConstants { // this goes in the class
private:		// header file
static const double FUDGE_FACTOR;
...
};
// this goes in the class implementation file
const double EngineeringConstants::FUDGE_FACTOR = 1.35;

大多数情况下你只要做这么多。唯一例外的是当你的类在编译时需要用到这个类的常量的情况,例如上面GamePlayer::scores数组的声明(编译过程中编译器一定要知道数组的大小)。所以,为了弥补那些(不正确地)禁止类内进行整型类常量初始化的编译器的不足,可以采用称之为“借用enum”的方法来解决。这种技术很好地利用了当需要int类型时可以使用枚举类型的原则,所以GamePlayer也可以象这样来定义:

 

class GamePlayer {
private:
enum { NUM_TURNS = 5 }	// "the enum hack" — makes
// NUM_TURNS a symbolic name 
// for 5
int scores[NUM_TURNS];// fine
};

除非你正在用老的编译器(即写于1995年之前),你不必借用enum。当然,知道有这种方法还是值得的,因为这种可以追溯到很久以前的时代的代码可是不常见的哟。

回到预处理的话题上来。另一个普遍的#define指令的用法是用它来实现那些看起来象函数而又不会导致函数调用的宏。典型的例子是计算两个对象的最大值:

 

#define max(a,b) ((a) > (b) ? (a) : (b))

这个语句有很多缺陷,光想想都让人头疼,甚至比在高峰时间到高速公路去开车还让人痛苦。
无论什么时候你写了象这样的宏,你必须记住在写宏体时对每个参数都要加上括号;否则,别人调用你的宏时如果用了表达式就会造成很大的麻烦。但是即使你象这样做了,还会有象下面这样奇怪的事发生:

 

int a = 5, b = 0;
max(++a, b);// a 的值增加了2次
max(++a, b+10); // a 的值只增加了1次

这种情况下,max内部发生些什么取决于它比较的是什么值!
幸运的是你不必再忍受这样愚笨的语句了。你可以用普通函数实现宏的效率,再加上可预计的行为和类型安全,这就是内联函数(见条款33):

 

inline int max(int a, int b) { return a > b ? a : b; }

不过这和上面的宏不大一样,因为这个版本的max只能处理int类型。但模板可以很轻巧地解决这个问题:

 

template<class T>
inline const T& max(const T& a, const T& b)
{ return a > b ? a : b; }

这个模板产生了一整套函数,每个函数拿两个可以转换成同种类型的对象进行比较然后返回较大的(常量)对象的引用。因为不知道T的类型,返回时传递引用可以提高效率(见条款22)。

顺便说一句,在你打算用模板写象max这样有用的通用函数时,先检查一下标准库(见条款49),看看他们是不是已经存在。比如说上面说的max,你会惊喜地发现你可以后人乘凉:max是C++标准库的一部分。
有了const和inline,你对预处理的需要减少了,但也不能完全没有它。抛弃#include的日子还很远,#ifdef/#ifndef在控制编译的过程中还扮演重要角色。预处理还不能退休,但你一定要计划给它经常放长假。

条款2:尽量用<iostream>而不用<stdio.h>

 

是的,scanf和printf很轻巧,很高效,你也早就知道怎么用它们,这我承认。但尽管他们很有用,事实上scanf和printf及其系列还可以做些改进。尤其是,他们不是类型安全的,而且没有扩展性。因为类型安全和扩展性是C++的基石,所以你也要服从这一点。另外,scanf/printf系列函数把要读写的变量和控制读写格式的信息分开来,就象古老的FORTRAN那样。是该向五十年代说诀别的时候了!

不必惊奇,scanf/printf的这些弱点正是操作符>>和<<的强项:

 

int i;
Rational r;// r 是个有理数
...
cin >> i >> r;
cout << i << r;

上面的代码要通过编译,>>和<<必须是可以处理Rational类型对象的重载函数(可能要通过隐式类型转换)。如果没有实现这样的函数,就会出错(处理int不用这样做,因为它是标准用法)。另外,编译器自己可以根据不同的变量类型选择操作符的不同形式,所以不必劳你去指定第一个要读写的对象是int而第二个是Rational。

另外,在传递读和写的对象时采用的语法形式相同,所以不必象scanf那样死记一些规定,比如如果没有得到指针,必须加上地址符,而如果已经得到了指针,又要确定不要加上地址符。这些完全可以交给C++编译器去做。编译器没别的什么事好做的,而你却不一样。最后要注意的是,象int这样的固定类型和象Rational这样的自定义类型在读写时方式是一样的。而你用sacnf和printf试试看!

你所写的表示有理数的类的代码可能象下面这样:

 

class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
...
private:
int n, d;// 分子,分母

 

friend ostream& operator<<(ostream& s, const Rational& ); }; ostream& operator<<(ostream& s, const Rational& r) { s<< r.n << '/' << r.d; return s; }

上面的代码涉及到operator<<的一些微妙(但很重要)的用法,这在本书其他地方详细讨论。例如:上面的operator<<不是成员函数(条款19解释了为什么),而且,传递给operator<<的不是Rational对象,而是定义为const的对象的引用(参见条款22)。operator>>的声明和实现也类似。

尽管我不大愿意承认,可有些情况下回到那些经过证明而且正确的老路上去还是很有意义的。第一,有些iostream的操作实现起来比相应的C stream效率要低,所以不同的选择会给你的程序有可能(虽然不一定,参见条款M16)带来很大的不同。但请牢记,这不是对所有的iostream而言,只是一些特殊的实现;参见条款M23。第二,在标准化的过程中,iostream库在底层做了很多修改(参见条款49),所以对那些要求最大可移植性的应用程序来说,会发现不同的厂商遵循标准的程度也不同。第三,iostream库的类有构造函数而<stdio.h>里的函数没有,在某些涉及到静态对象初始化顺序的时候,如果可以确认不会带来隐患,用标准C库会更简单实用。

iostream库的类和函数所提供的类型安全和可扩展性的价值远远超过你当初的想象,所以不要仅仅因为你用惯了<stdio.h>而舍弃它。毕竟,转换到iostream后,你也不会忘掉<stdio.h>。

顺便说一句,本条款的标题没有打印错;我确实说的是<iostream>而非<iostream.h>。从技术上说,其实没有<iostream.h>这样的东西——标准化委员会在简化非C标准头文件时用<iostream>取代了它。他们这样做的原因在条款49进行了解释。还必须知道的是,如果编译器同时支持 <iostream>和<iostream.h>,那头文件名的使用会很微妙。例如,如果使用了#include <iostream>, 得到的是置于名字空间std(见条款28)下的iostream库的元素;如果使用#include <iostream.h>,得到的是置于全局空间的同样的元素。在全局空间获取元素会导致名字冲突,而设计名字空间的初衷正是用来避免这种名字冲突的发生。还有,打字时<iostream>比<iostream.h>少两个字,这也是很多人用它的原因。:)

 

条款3:尽量用new和delete而不用malloc和free

 

malloc和free(及其变体)会产生问题的原因在于它们太简单:他们不知道构造函数和析构函数。

假设用两种方法给一个包含10个string对象的数组分配空间,一个用malloc,另一个用new:

  

string *stringarray1 =
static_cast<string*>(malloc(10 * sizeof(string)));
string *stringarray2 = new string[10];

其结果是,stringarray1确实指向的是可以容纳10个string对象的足够空间,但内存里并没有创建这些对象。而且,如果你不从这种晦涩的语法怪圈(详见条款m4和m8的描述)里跳出来的话,你没有办法来初始化数组里的对象。换句话说,stringarray1其实一点用也没有。相反,stringarray2指向的是一个包含10个完全构造好的string对象的数组,每个对象可以在任何读取string的操作里安全使用。

假设你想了个怪招对stringarray1数组里的对象进行了初始化,那么在你后面的程序里你一定会这么做:

 

free(stringarray1);
delete [] stringarray2;// 参见条款5:这里为什么要加上个"[]"

调用free将会释放stringarray1指向的内存,但内存里的string对象不会调用析构函数。如果string对象象一般情况那样,自己已经分配了内存,那这些内存将会全部丢失。相反,当对stringarray2调用delete时,数组里的每个对象都会在内存释放前调用析构函数。

既然new和delete可以这么有效地与构造函数和析构函数交互,选用它们是显然的。

把new和delete与malloc和free混在一起用也是个坏想法。对一个用new获取来的指针调用free,或者对一个用malloc获取来的指针调用delete,其后果是不可预测的。大家都知道“不可预测”的意思:它可能在开发阶段工作良好,在测试阶段工作良好,但也可能会最后在你最重要的客户的脸上爆炸。

new/delete和malloc/free的不兼容性常常会导致一些严重的复杂性问题。举个例子,<string.h>里通常有个strdup函数,它得到一个char*字符串然后返回其拷贝:

 

char * strdup(const char *ps);	// 返回ps所指的拷贝

在有些地方,c和c++用的是同一个strdup版本,所以函数内部是用malloc分配内存。这样的话,一些不知情的c++程序员会在调用strdup后忽视了必须对strdup返回的指针进行free操作。为了防止这一情况,有些地方会专门为c++重写strdup,并在函数内部调用了new,这就要求其调用者记得最后用delete。你可以想象,这会导致多么严重的移植性问题,因为代码中strdup以不同的形式在不同的地方之间颠来倒去。

c++程序员和c程序员一样对代码重用十分感兴趣。大家都知道,有大量基于malloc和free写成的代码构成的c库都非常值得重用。在利用这些库时,最好是你不用负责去free掉由库自己malloc的内存,并且/或者,你不用去malloc库自己会free掉的内存,这样就太好了。其实,在c++程序里使用malloc和free没有错,只要保证用malloc得到的指针用free,或者用new得到的指针最后用delete来操作就可以了。千万别马虎地把new和free或malloc和delete混起来用,那只会自找麻烦。

既然malloc和free对构造函数和析构函数一无所知,把malloc/free和new/delete混起来用又象嘈杂拥挤的晚会那样难以控制,那么,你最好就什么时候都一心一意地使用new和delete吧。
 

条款4:尽量使用c++风格的注释

 

旧的c注释语法在c++里还可以用,c++新发明的行尾注释语法也有其过人之处。例如下面这种情形:

 

if ( a > b ) {
// int temp = a;	// swap a and b
// a = b;
// b = temp;
}

假设你出于某种原因要注释掉这个代码块。从软件工程的角度看,写这段代码的程序员也做得很好,他最初的代码里也写了一个注释,以解释代码在做什么。用c++形式的句法来注释掉这个程序块时,嵌在里面的最初的注释不受影响,但如果选择c风格的注释就会发生严重的错误:

 

if ( a > b ) {
/*	int temp = a;  /* swap a and b */
a = b;
b = temp;
*/
}

请注意嵌在代码块里的注释是怎么无意间使本来想注释掉整个代码块的注释提前结束的。

c风格的注释当然还有它存在的价值。例如,它们在c和c++编译器都要处理的头文件中是无法替代的。尽管如此,只要有可能,你最好尽量用c++风格的注释。

值得指出的是,有些老的专门为c写的预处理程序不知道处理c++风格的注释,所以象下面这种情形时,事情就不会象预想的那样:

 

#define light_speedp 3e8	// m/sec (in a vacuum)

对于不熟悉c++的预处理程序来说,行尾的注释竟然成为了宏的一部分!当然,正象条款1所说的那样,你无论如何也不会用预处理来定义常量的。

 

这篇关于Cplusplus topic的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/718174

相关文章

ActiveMQ—Queue与Topic区别

Queue与Topic区别 转自:http://blog.csdn.net/qq_21033663/article/details/52458305 队列(Queue)和主题(Topic)是JMS支持的两种消息传递模型:         1、点对点(point-to-point,简称PTP)Queue消息传递模型:         通过该消息传递模型,一个应用程序(即消息生产者)可以

MQTT: Topic Names and Topic Filters

Topic Names and Topic Filters Topic wildcards Topic Name 和 Topic Filter 的区别就是 Topic Name 不能包含通配符,而 Topic Filter 可以包含通配符。 Topic Name 标识一个具体的主题,而 Topic Filter 可以标识一个或者一组主题。 Topic level separator

宏__cplusplus/////ifnbsp;define…

在C与C++混合编写的代码中,我们常常会在头文件里看到如下的声明:   #ifdef __cplusplus //如果定义了表示是c++编译器extern "C" {  #endif  // 在这里写标准c程序   #ifdef __cplusplus  }  #endif   __cplusplus是c++编译器内部定义的宏,如果使用的c编译器__cplusplus不会被定义,所以它用来区分

mindManager的topic中换行的实现

mindmanager 9中,在topic或sub topic中输入中文文字时 ,如果文字较多的话,会显得很长,但是用enter键却不能分行,这样就无法调整其宽度,图显得有点难看。上网查了一下有解决办法并试了一下,有效! 在MindManager里Enter(回车键)用于确认内容,换行方式变为: 1、ctrl+enter; 2、shift+enter也可以; 3、在需要换行的地方按

SpringBoot集成kafka-指定topic(主题)-partition(分区)-offset(偏移量)消费信息

SpringBoot集成kafka-指定topic-partition-offset消费信息 1、消费者2、生产者3、配置类4、配置文件5、实体类6、工具类7、测试类8、第一次测试(读取到19条信息)9、第二次测试(读取到3条信息) 1、消费者 指定消费者读取配置文件中 topic = " k a f k a . t o p i c . n a m e " , g r o u

Kakfa的核心概念-Replica副本(kafka创建topic并指定分区和副本的两种方式)

Kakfa的核心概念-Replica副本(kafka创建topic并指定分区和副本的两种方式) 1、kafka命令行脚本创建topic并指定分区和副本2、springboot集成kafka创建topic并指定分区和副本2.1、springboot集成kafka2.1.1、springboot集成kafka创建topic并指定5个分区和1个副本2.1.2、往分区中发送消息2.1.3、sprin

Kafka学习笔记 --- Topic 与 offset

我们知道流处理平台有以下三种特性:   * 可以让你发布和订阅流式的记录。这一方面与消息队列或者企业消息系统类似。 * 可以储存流式的记录,并且有较好的容错性。 * 可以在流式记录产生时就进行处理。   Kafka适合什么样的场景?   它可以用于两大类别的应用:   * 构造实时流数据管道,它可以在系统或应用之间可靠地获取数据。 (相当于message queue)

Kafka学习笔记 --- 操作Topic

1.添加和删除Topic 用户可以手动的添加Topic,或者首次将数据发布到不存在Topic时自动创建Topic。 如果自动创建Topic,需要了解一下相关的默认配置: 与Topic相关 的配置有服务器默认值和可选的按Topic覆盖,如果提供Topic的配置,则可以使用服务器的默认配置,在创建Topic的时候通过提供一个或者多个—config选项来设置代替值。本示例使用自定义的最大邮件大小和刷新

Kafka篇之清理或创建topic

1. kafka清理topic主题 清理topic步骤: step1: ./kafka-topics.sh --bootstrap-server 10.143.167.41:9092,10.143.167.42:9092,10.143.167.43:9092 --delete --topic reflow_data_topic 请注意,如果 Kafka 的配置中没有设置 delete.to

Kafka 学习笔记(二)--- topic命令行操作

上一篇介绍了kafka的特点,这篇来记录kafka的主要工作方式: 1.创建Topic, 2. 生产者生产  3.消费者消费 首先 创建 一个 test topic, 命令如下: ./kafka-topics.sh --zookeeper 172.28.201.217:2181 --create --topic test --replication-factor 2 --partitions