`
conkeyn
  • 浏览: 1505123 次
  • 性别: Icon_minigender_1
  • 来自: 厦门
社区版块
存档分类
最新评论

C++的11个注意要点

阅读更多

C++ 11 个注意要点

  下面的这些要点是对所有的 C++ 程序员都适用的。我之所以说它们是最重要的,是因为这些要点中提到的是你通常在 C++ 书中或网站上无法找到的。如:指向成员的指针,这是许多资料中都不愿提到的地方,也是经常出错的地方,甚至是对一些高级的 C++ 程序员也是如此。  
  这里的要点不仅仅是解释怎样写出更好的代码,更多的是展现出语言规则里面的东西。很显然,它们对 C++ 程序员来说是永久的好资料。我相信这一篇文章会使你收获不小。  

  首先,我把一些由不同层次的 C++ 程序员经常问的问题归到一起。我惊奇的发现有很多是有经验的程序员都还没意识到 .h 符号是否还应该出现在标准头文件中。  


要点 1: <iostream.h> 还是 <iostream>?  

  很多 C++ 程序员还在使用 <iostream.h> 而不是用更新的标准的 <iostream> 库。这两者都有什么不同呢?首先, 5 年前我们就开始反对把 .h 符号继续用在标准的头文件中。继续使用过时的规则可不是个好的方法。从功能性的角度来讲, <iostream> 包含了一系列模板化的 I/O 类,相反地 <iostream.h> 只仅仅是支持字符流。另外,输入输出流的 C++ 标准规范接口在一些微妙的细节上都已改进,因此, <iostream> <iostream.h> 在接口和执行上都是不同的。最后, <iostream> 的各组成都是以 STL 的形式声明的,然而 <iostream.h> 的各组成都是声明成全局型的。  

  因为这些实质上的不同,你不能在一个程序中混淆使用这两个库。做为一种习惯,在新的代码中一般使用 <iostream> ,但如果你处理的是过去编写的代码,为了继承可以用继续用 <iostream.h> 旧保持代码的一致性。    


要点 2 :用引用传递参数时应注意的地方  

  在用引用传递参数时,最好把引用声明为 const 类型。这样做的好处是:告诉程序不能修改这个参数。在下面的这个例子中函数 f() 就是传递的引用:  

void f(const int & i); 
int main() 

  f(2); /* OK */ 


  这个程序传递一个参数 2 f() 。在运行时, C++ 创建一个值为 2 int 类型的临时变量,并传递它的引用给 f(). 这个临时变量和它的引用从 f() 被调用开始被创建并存在直到函数返回。返回时,就被马上删除。注意,如果我们不在引用前加上 const 限定词,则函数 f() 可能会更改它参数的值,更可能会使程序产生意想不到的行为。所以,别忘了 const  

  这个要点也适用于用户定义的对象。你可以给临时对象也加上引用如果是 const 类型:  

struct A{}; 
void f(const A& a); 
int main() 

  f(A()); // OK, 传递的是一个临时 A const 引用  



要点 3 逗号分离 表达形式  

  逗号分离 表达形式是从 C 继承来的,使用在 for- while- 循环中。当然,这条语法规则被认为是不直观的。首先,我们来看看什么是 逗号分离 表达形式。  

  一个表达式由一个或多个其它表达式构成,由逗号分开,如:  

  if(++x, --y, cin.good()) // 三个表达式  
  这个 if 条件包含了三个由逗号分离的表达式。 C++ 会计算每个表达式,但完整的 逗号分离 表达式的结果是最右边表达式的值。因此,仅当 cin.good() 返回 true 时, if 条件的值才是 true 。下面是另一个例子:  
int j=10; 
int i=0; 
while( ++i, --j) 

  // 直到 j 0 时,循环结束,在循环时, i 不断自加  


要点 4 ,使用全局对象的构造函数在程序启动前调用函数  

  有一些应用程序需要在主程序启动前调用其它函数。如:转态过程函数、登记功能函数都是必须在实际程序运行前被调用的。最简单的办法是通过一个全局对象的构造函数来调用这些函数。因为全局对象都是在主程序开始前被构造,这些函数都将会在 main() 之前返回结果。如:  
class Logger 


  public: 
  Logger() 
  
    activate_log();// 译者注:在构造函数中调用你需要先运行的函数  
  
}; 
Logger log; //
一个全局实例  

int main() 

  record * prec=read_log();// 译者注:读取 log 文件数据  
  //.. 程序代码  



  全局对象 log main() 运行之前被构造, log 调用了函数 activate_log() 。从而,当 main() 开始执行时,它就可以从 log 文件中读取数据。  


  毫无疑问地,在 C++ 编程中内存管理是最复杂和最容易出现 bug 的地方。直接访问原始内存、动态分配存储和最大限度的发挥 C++ 指令效率,都使你必须尽力避免  

有关内存的 bug  
    
要点 5 :避免使用复杂构造的指向函数的指针  

  指向函数的指针是 C++ 中可读性最差的语法之一。你能告诉我下面语句的意思吗?  

void (*p[10]) (void (*)()); 
   P 是一个 10 个指针构成的指向一个返回 void 类型且指向另一个无返回和无运算的函数的数组 。这个麻烦的语法真是让人难以辨认,不是吗?你其实可以简单的通过 typedef 来声明相当于上面语句的函数。首先,使用 typedef 声明 指向一个无返回和无运算的函数的指针  
typedef void (*pfv)(); 
  接着,声明 另一个指向无返回且使用 pfv 的函数指针  
typedef void (*pf_taking_pfv) (pfv); 
  现在,声明一个由 10 个上面这样的指针构成的数组:  
pf_taking_pfv p[10]; 
  与 void (*p[10]) (void (*)()) 达到同样效果。但这样是不是更具有可读性  
了!  

要点 6 :指向成员的指针  

  一个类有两种基本的成员:函数成员和数据成员。同样的,指向成员的指针也有两种:指向函数成员的指针和指向数据成员的指针。后则其实并不常用,因为类一般是不含有公共数据成员的,仅当用在继承用 C 写的代码时协调结构( struct) 和类 (class) 时才会用到。  

  指向成员的指针是 C++ 语法中最难以理解的构造之一,但是这也是一个 C++ 最强大的特性。它可以让你调用一个类的函数成员而不必知道这个函数的名字。这一个非常敏捷的调用工具。同样的,你也可以通过使用指向数据成员的指针来检查并改变这个数据而不必知道它的成员名字。  

  指向数据成员的指针  

  尽管刚开始时,指向成员的指针的语法会使你有一点点的迷惑,但你不久会发现它其实同普通的指针差不多,只不过是 * 号的前面多了 :: 符号和类的名字,例:定义一个指向 int 型的指针:  


int * pi; 
  定义一个指向为 int 型的类的数据成员:  
int A::*pmi; //pmi
是指向类 A 的一个 int 型的成员  
  你可以这样初始化它:  
class A 

  public: 
  int num; 
  int x; 
}; 
int A::*pmi = & A::num; 
  上面的代码是声明一个指向类 A 的一个 int 型的 num 成员并将它初始化为这个 num 成员的地址 . 通过在 pmi 前面加上 * 你就可以使用和更改类 A num 成员的值:  
A a1, a2; 
int n=a1.*pmi; //
a1.num 赋值给
a1.*pmi=5; //
5 赋值给 a1.num 
a2.*pmi=6; //
6 赋值给 6a2.num 

  如果你定义了一个指向类 A 的指针,那么上面的操作你必须用 ->* 操作符代替:  
A * pa=new A; 
int n=pa->*pmi; 
pa->*pmi=5; 

  指向函数成员的指针  

  它由函数成员所返回的数据类型构成,类名后跟上 :: 符号、指针名和函数的参数列表。举个例子:一个指向类 A 的函数成员(该函数返回 int 类型)的指针:  

class A 

  public: 
  int func (); 
}; 
int (A::*pmf) (); 

  上面的定义也就是说 pmf 是一个指向类 A 的函数成员 func() 的指针 . 实际上,这个指针和一个普通的指向函数的指针没什么不同,只是它包含了类的名字和 :: 符号。你可以在在任何使用 *pmf 的地方调用这个函数  
func()
 
pmf=&A::func; 
A a; 
(a.*pmf)(); //
调用 a.func() 
  如果你先定义了一个指向对象的指针,那么上面的操作要用 ->* 代替:  
A *pa=&a; 
(pa->*pmf)(); //
调用 pa->func() 
  指向函数成员的指针要考虑多态性。所以,当你通过指针调用一个虚函数成员时,这个调用将会被动态回收。另一个需要注意的地方,你不能取一个类的构造函数和析构函数的地址。  

要点 7 、避免产生内存碎片  


  经常会有这样的情况:你的应用程序每运行一次时就因为程序自身缺陷而产生内存漏洞而泄漏内存,而你又在周期性地重复着你的程序,结果可想而知 , 它也会使系统崩溃。但怎样做才能预防呢?首先,尽量少使用动态内存。在大多数情况下,你可能使用静态或自动存储或者是 STL 容器。第二,尽量分配大块的内存而不是一次只分配少量内存。举个例子:一次分配一个数组实例所需的内存,而不是一次只分配一个数组元素的内存。  

要点 8 、是 delete 还是 delete[] 

  在程序员中有个荒诞的说法:使用 delete 来代替 delete[] 删除数组类型时是可以的!  
  举个例子吧:  

  int *p=new int[10]; 
  delete p; // 错误,应该是: delete[] p 
  上面的程序是完全错误的。事实上,在一个平台上使用 delete 代替 delete[] 的应用程序也许不会造成系统崩溃,但那纯粹是运气。你不能保证你的应用程序是不是会在另一个编译器上编译,在另一个平台上运行,所以还是请使用 delete[]  

要点 9 、优化成员的排列  

  一个类的大小可以被下面的方式改变:  

struct A 


  bool a; 
  int b; 
  bool c; 
}; //sizeof (A) == 12 

  在我的电脑上 sizeof (A) 等于 12 。这个结果可能会让你吃惊,因为 A 的成员总数是 6 个字节: 1+4+1 个字节。那另 6 字节是哪儿来的?编译器在每个 bool 成员后面都插入了 3 个填充字节以保证每个成员都是按 4 字节排列,以便分界。你可以减少 A 的大小,通过以下方式:  

struct B 

  bool a; 
  bool c; 
  int b; 
}; // sizeof (B) == 8 

  这一次,编译器只在成员 c 后插入了 2 个字节。因为 b 占了 4 个字节,所以就很自然地把它当作一个字的形式排列,而 a c 的大小 1+1=2 ,再加上 2 个字节就刚好按两个字的形式排列 B  

要点 10 、为什么继承一个没有虚析构函数的类是危险的?  

  一个没有虚析构函数的类意味着不能做为一个基类。如 std::string,std::complex, std::vector 都是这样的。为什么继承一个没有虚析构函数的类是危险的?当你公有继承创建一个从基类继承的相关类时,指向新类对象中的指针和引用实际上都指向了起源的对象。因为析构函数不是虚函数,所以当你 delete 一个这样的类时, C++ 就不会调用析构函数链。举个例子说明:  

class A 

  public: 
  ~A() // 不是虚函数  
 
  // ... 
 
}; 
class B: public A //
; A 没有虚析构函数  

  public: 
  ~B() 
 
  // ... 
 
}; 

int main() 

  A * p = new B; // 看上去是对的  
  delete p; // 错, B 的析构函没有被调用  


要点 11 、以友元类声明嵌套的类  

  当你以友元类声明一个嵌套的类时,把友元声明放在嵌套类声明的后面,而不前面。  

class A 

  private: 
  int i; 
  public: 
  class B // 嵌套类声明在前  
 
   public: 
   B(A & a) { a.i=0;}; 
  }; 
  friend class B;// 友元类声明  
}; 

分享到:
评论

相关推荐

    学习C++需要注意的50要点

    学习C++需要注意的50要点

    你最需要注意的11条要点for c++

    你最需要注意的11条要点for c++

    C++多线程编程介绍,技巧及注意要点.pptx

    C++多线程编程介绍,技巧以及注意要点,主要以C++11和C++14作为主要语言进行介绍。

    C++多线程编程介绍,技巧及注意要点--Part2.pptx

    C++多线程编程介绍,技巧以及注意要点,主要以C++11和C++14作为主要语言进行介绍。本节主要介绍原子变量和内存顺序。

    C++多线程编程介绍,技巧及注意要点-Part_3.pptx

    C++多线程编程介绍,技巧以及注意要点,主要以C++11和C++14作为主要语言进行介绍。本节主要介绍异步编程及其技巧和常见异步框架分析。

    程序员必须知晓的11个C++要点-供大家学习研究参考

    由函数成员所返回的数据类型构成,类名后跟上::符号、指针名和函数的参数列表。举个例子:一个指向类A的函数成员(该函数返回int类型)的指针: ...另一个需要注意的地方,你不能取一个类的构造函数和析构函数的地址。

    C++重要知识点分析

    C++重要知识点分析,这正是学习以及应用过程中应该注意要点

    c++语法精品总结

    c++ 相关的要点,注意点,区分点等,以及cpp 的相关运算符比较。

    C++ Builder 5 编程实例与技巧

    本书具有下面的一些特色: (1) 例程精练、实用、完整,易于查找本书的...值得注意的是,第一部分的内容对使用C++语言进行软件开发非常有用,一定要掌握好。对于C++Builder的高级用户,可以直接挑选感兴趣的章节阅读。

    Visual c++通用范例开发金典 韦朴 源代码

    读范例金典,走开发捷径,11大类编程应用,31个主要技术方向,112个细分应用点,355个经典范例,应用为纲、范例为目,深入介绍Visual C++的约722个重要程序代码,精要阐释每个范例实现原理、开发过程和编程要点。...

    C++ Builder_5 编程实例与技巧(源代码)

    本书特色 本书具有下面的一些特色: (1) 例程精练、实用、完整,易于...值得注意的是,第一部分的内容对使用C++语言进行软件开发非常有用,一定要掌握好。对于C++Builder的高级用户,可以直接挑选感兴趣的章节阅读。

    C++各变量的区别

    C++ 全面基础知识要点之变量 1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,...

    《C++游戏编程入门教程》笔记

    一个初学者认为此书中对C++游戏编程基础知识介绍时,读者应该注意的要点

    C++语言:switch语句最详细讲解.pdf

    C++语言:switch语句最详细讲解。从switch语句的执行过程,switch语句的注意要点,例题讲解,作业等 。适合于中小学生,信息学爱好者。

    Visual C++通用范例开发金典 源码

    读范例金典,走开发捷径,11大类编程应用,31个主要技术方向,112个细分应用点,355个经典范例,应用为纲、范例为目,深入介绍Visual C++的约722个重要程序代码,精要阐释每个范例实现原理、开发过程和编程要点。...

    C++MFC教程

    4、窗口句柄:说到消息就不能不说窗口句柄,系统通过窗口句柄来在整个系统中唯一标识一个窗口,发送一个消息时必须指定一个窗口句柄表明该消息由那个窗口接收。而每个窗口都会有自己的窗口过程,所以用户的输入就会...

    Visual C++串口通信技术与工程实践

    【内容简介】 本书详细介绍了利用Visual C++进行串口通信编程的...1.3.3 RS-422与RS-485网络安装注意要点 15 1.3.4 RS-422与RS-485的接地问题 16 1.4 IEEE 1284 1994并口标准 17 1.4.1 并行打印机端口概述 17

    程序员编程进价的要点

    程序员进价应该注意的要点,编程的方法,技巧,以及学习心得。

    基于C++开发的射击游戏

    3、计算常量dx,dy,2dy和2dy-2dx,并得到决策参数的第一个值: d0 = 2dy-dx 4、从k=0开始,在沿线路径的每个xk处,进行下列检测: 如果dk,下一个要绘制的点是(xk+1,yk),并且 dk+1 = dk+2dy 否则,下一个...

    Visual C++/Turbo C串口通信编程实践 及源代码-1

    11.3.3 rs-422与rs-485的网络安装注意要点 317 11.3.4 rs-232、rs422、rs485电气参数对比 318 11.4 串口调试注意事项 318 11.5 常用数据校验法 318 11.5.1 奇偶校验 318 11.5.2 循环冗余码校验 319 11.6 串口...

Global site tag (gtag.js) - Google Analytics