编程中国之高质量C++或C编程指南学习笔记

来源:互联网 发布:三工鸟王其网络班 编辑:程序博客网 时间:2024/05/28 15:08

声明,本文内容均为学习后的总结,若有出入,请指正。以下为正文。

1章 前言

 

1、对一个文件是否好考察需要以下几个方面:

A、编程风格;

B、出错处理;

C、算法复杂度分析(用于提高性能)。

 

2章 文件结构

 

2.1 版权和版本的声明:

版权和版本的声明位于头文件和定义文件的开头,主要内容有:

A、版本信息;

B、文件名称,标识符,摘要;

C、当前版本号,作者(或修改者),完成日期;

D、版本历史信息。

 

2.2为了防止头文件被重复引用,应当用ifndef/define/endif结构产生预处理块。

2.3 用#include <filename.h>来引用标准库的头文件,用#include “filename.h”来引用非标准库的头文件。

2.4 头文件中只存放声明,而不存放定义。

2.5 不提倡使用全局变量,尽量不要在文件中出现extern int value这类声明。

 

3章 程序的版式

3.1 空行。

3.2 一行代码只做一件事。

3.3 if ,for, while, do等语句独占一行。

3.4 应尽量在定义变量的时候初始化它(就近原则)。

3.5 每行代码最好控制在7080个字之间。

3.6 表达式要在低优先级操作符处拆分成新行,操作符放在新行之首。

 

4章 命名规则

4.1 程序中不要出现标识符完全相同的局部变量和全局变量。

4.2 变量的名字应当使用 形容词+名词的形式。

4.3 全局函数应当使用 动词+名词的形式,局部函数使用动词。例如:DrawBox(); //全局函数,Draw(); //局部函数。

4.4 用正确的反义词命名具有互斥意义的变量或相反动作的函数。

4.5 尽量避免名字中出现数字编号,除非逻辑上确实需要编号。

4.6 变量和参数用小写字母开头的单词组合而成,常量全用大写字母,用下划线分割单词。

4.7 静态变量前加s_,全局变量前加g_,类的成员变量前加m_


5章 常量

5.1 C语言用#define来定义常量(宏常量),C++语言除了用#define,还可以用const来定义常量。

例如:#define MAX 10      

const int MAX = 100;

 

5.2 const#define的比较

A、const有数据类型,#define没有数据类型。编译器可以对前者进行安全类型检查,对后者却没有,只是进行字符替换,在字符替换时可能出现边际效应。

B、有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。

5.3 不能再类的声明中初始化const常量,应为类的对象在没有被创建时,还不知道size的值是什么。

 

6章 函数设计

6.1 参数的规则

参数的书写要完整,不要只写类型而不写名字。如果没有参数,则用void填充。

void SetValue(int height, int width);//良好void  SetValue(int, int);//不良float GetValue(void);//良好float GetValue();//不良

6.2 参数名字要恰当,顺序要合理

6.3 如果参数是指针,且仅作为输入用,则应在类型前加const,以防止指针在函数内被意外的修改。

6.4 如果输入参数以值传递的方式传递对象,则改为”const &”方式。

6.5 避免函数有太多的参数,最好不要超过5个。

6.6 不要使用类型和数目不确定的参数。

6.7 不要省略返回值的类型。

6.8 不要将正常值和错误标志放在一起返回。   

 

7章 内存管理

7.1 内存分配的三种方式:

A、从静态存储区分配。内存在程序编译的时候就已经分配好了,在程序运行的整个过程中都存在。例如全局变量。Static变量。      

B、在栈上创建。在函数执行的时候,函数内局部变量在栈上创建,在函数运行结束后,这些内存被自动销毁。优点是效率高,缺点是分配的内存有限。

C、从堆上分配,也称为动态内存分配。程序在运行的时候使用mallocnew申请的任意的内存,在程序结束时用freedelete释放掉。它的生存期由程序员决定,比较灵活,但是也容易出现问题。

 

7.2 常见的内存错误及对策:

A、内存分配没有成功,就使用了它。

B、内存虽然分配成功了,但是没有初始化就使用了它。

C、内存分配成功,也初始化了,但是操作越界了。

D、忘记释放内存,导致内存泄露了。

E、释放了内存,但是继续使用它。

7.3 释放内存问题有以下三种情况:

A、程序中对象的调用关系过于复杂,难以搞清楚某一个对象是否已经释放了内存;

B、函数的return语句写错了,注意,不要返回指向栈内存引用,因为此时该内存在函数结束时已被销毁;

C、使用freedelete后,没有将指针置为NULL,导致出现野指针

 

7.4 【规则】使用mallocnew申请内存之后,应该立即检查指针值是否为NULL,防止使用指针值为NULL的内存。

7.5 【规则】不要忘记给数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

7.6 【规则】避免数组和指针的下标越界。特别当心发生多1或者少1的情况。

7.7 【规则】动态内存的申请和释放必须配对,防止出现内存泄露。

7.8 【规则】用freedelete释放内存后,应立即将指针置为NULL,防止出现野指针

7.9 计算内存容量:当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。用sizeof能计算出数组的容量,却不能计算出指针所指内存的容量,除非在申请内存的时候记住它。

例子:

Void GetValue(char str[100])

{

Cout << sizeof(str) << endl;//4字节而不是100字节

}

7.10 指针参数是如何传递内存的

如果一个函数的参数是一个指针,不要指望它去申请内存。

7.11 野指针:

a) 野指针 不是NULL指针,它是指向垃圾的指针,一般人们不会用错NULL指针,因为用if语句很好判断,但是野指针很危险,if语句对它不起作用。

b) 野指针 的成因主要有两种。

c) 指针没有被初始化。任何指针在刚刚创建的时候都不会称为NULL指针,它的缺省值是随机的,它会乱指一气。所以指针在创建的同时应该被初始化,要么将指针置为NULL,要么指向合法的内存。例如:char *p = NULL;   char *str = (char*)malloc(100);

d) 指针pfreedelete后,没有置为NULL,被人认为是合法的内存。

e) 指针超越了变量的作用范围。

 

第8章 C++的 高级特性

对比C语言的函数,C++增加了重载(overload)、内联(inline)constvirtual四种新机制。其中overloadinline既可以用于全局函数,也可以用于成员函数。constvirtual仅可用于成员函数。

C++函数需要overload的另一个理由:类的构造函数需要重载机制。

8.1 overload的实现

几个函数名相同的函数依旧是不同的函数,使用了返回值和参数来区分。

如果同名函数的参数(顺序,类型)不同,那么容易区分它们是不同的函数。

如果同名的函数仅仅是返回值不同,有时可以区分,有时却不行。所以,只能靠参数,而不能靠返回值来区分overload函数。

并不是函数名相同就能构成overload,全局函数和成员函数同名不算overload,因为它们的作用域不同。

8.2 成员函数的重载(overload),覆盖(override)和隐藏。

overloadoverride

成员函数被overload的特征:

相同的范围(同一个类中);函数名相同;参数不同;virtual关键字可有可无。

override是指派生类函数override基类函数:

不同的范围(分别位于派生类和基类);函数名相同;参数相同;基类函数必须有virtual关键字。以下为例子:

#include class Base{public:void f(int x){cout << “Base::f(int)” << x << endl;}void f(float y){cout << ”Base::f(float)” << y << endl;}//以上两个函数的关系是overloadvirtual void g(void) {cout << “Base::g(void)” << endl;}};

class Derived::public Base{public:virtual void g(void){cout << “Derived::g(void)” << end;}//该函数是对Base类中g(void)函数的override};

void main(void){Derived d;Base *pb = &d;pb->f(42);//Base::f(int) 42pb->f(3.14f);//Base::f(float) 3.14pb->g(); //Derived::g(void)}

8.3 隐藏

A、如果派生类与基类的函数名相同,但是参数不同。此时,无论基类函数有无virtual关键字,基类的函数将被隐藏。

B、如果派生类与基类的函数名和参数都相同,但是基类没有virtual关键字。此时,基类的函数将被隐藏。

 

8.4 函数的缺省值

A、函数的缺省值只能出现在函数的声明中,而不能出现在函数的定义中。

B、如果函数有多个参数,参数只能从后往前依次缺省。

 

第9章 类的构造函数、析构函数和赋值函数

每一个类只有一个析构函数和一个赋值函数,但是可以有多个构造函数(包括一个拷贝构造函数,其他的称为普通构造函数)。对于函数A,如果不想编写上述函数,C++编译器将会自动产生四个缺省的函数,如:

A(void);//缺省的无参数构造函数

A(const A &a);//缺省的拷贝构造函数

~A(void);//缺省的析构函数

A & operate =(const A &a);//缺省的赋值函数

 

9.1 String类的设计与实现

class String{public:String(const char *str = NULL);//普通构造函数String(const String &other);//拷贝构造函数~String(void);String & operate =(const String &other);//赋值函数 private:char *m_data;//用于保存字符串};

9.2 String类的普通构造函数和析构函数

String::String(const char *str){if(str == NULL){m_data = new char[1];*m_data = ‘\0’;}else{int len = sizeof(str);m_data = new char[len+1];strcpy(m_data, str);}}

String::~String(void){delete [] m_data;//由于m_data是内部数据类型,所以也可以写成delete m_data;}

9.3 String类的拷贝构造函数与赋值函数

String::String(const String &other){//允许操作other的私有成员m_dataint len = sizeof(other.m_data);m_data = new char[len+1];strcpy(m_data, other.m_data);}

String & String::operate =(const String &other){//检查自赋值if(this == &other){return *this;}//释放原有的内存delete [] m_data;//分配新的内存资源,并赋值内容int len = sizeof(other.m_data)m_data = new char[len+1];strcpy(m_data, other.m_data);//返回本对象的调用return *this;}

9.4 如何在派生类中实现类的基本函数

基类的构造函数,析构函数和赋值函数都不能被派生类继承。如果类之间存在继承关系,在编写上述函数时需要注意:

A、派生类的构造函数应该在其初始化表里调用基类的构造函数。

B、 基类与派生类的析构函数应该为虚。

 

第10章 类的继承与组合

10.1 继承

如果A是基类,BA类的派生类,则B将继承A类的数据和函数。

10.2 【规则】如果A类和B类毫不相关,不可以为了使B的功能更多而让B继承A的功能和属性。

10.3 【规则】若在逻辑上BA的一种,则允许B继承A的功能和属性。

10.4 【规则】看起来很简单,但是实际的应用中可能会出现意外,继承的概念在程序世界和现实世界并不是完全相同。例如:鸵鸟是鸟的一种,但是Ostrich::Fly()则不能。

 

11

11.1 const

constconstant的缩写,是“恒定不变”的意思。被const修饰的东西都受到强制性保护,可以防止意外的变动,能提高程序的健壮性。

11.2 const修饰函数的参数

如果参数作为输出用,不论它是什么类型,也不论它采用 指针传递 还是 引用传递,都不能加const修饰,否则该参数将失去输出功能。const只能修饰输入参数,如果输入参数采用 指针传递,那么加const修饰可以防止意外的变动,起到保护的作用。如果输入参数采用 值传递,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无须保护,所以不需要加const修饰。

对于非内部数据类型的输入参数,应该将“值传递”改为“const引用传递”,目的是提高效率。例如将void fun(A a);改为void fun(const A &a)。对于内部数据类型的输入参数,不要将“值传递”改为“const引用传递”。

11.3 const修饰函数的返回值

11.4 任何不会修改数据成员的函数都应该声明为const类型

以下程序中,类Stack的成员函数GetCount(void)仅用做计数,从逻辑上讲GetCout(void)应该为const函数。

class Stack{public:void Push(int elem);int Pop(void);int GetCount(void) const;//const成员函数 private:int m_num;int m_data[10];};

int Stack::GetCount(void) const{++ m_num;//编译错误,企图修改数据成员Pop();//编译错误,企图调用非const函数return m_num;}

const成员函数的声明看起来怪怪的,const关键字只能放在函数声明的尾部,大概是因为其他地方都被占用了。

 

11.5 【规则】不要一味的追求程序的效率,应当满足正确性,可靠性,健壮性和可读性的前提下,设法提高程序的效率。

11.6 【规则】以提高程序的全局效率为主,以提高局部效率为辅。

11.7 【规则】在优化程序的效率时,应当找出限制效率的“瓶颈”,不要在无关紧要处优化。

11.8 【建议】先优化数据结构和算法,再优化执行代码。

11.9 【建议】当心那些视觉上不容易区分的操作符发生书写错误。

11.10 【建议】变量(指针或数组)被创建后应该立即初始化,避免把未初始化的变量当成右值来使用。

11.11 【建议】当心数据类型转换发生错误。

11.12 【建议】当心变量的处置初值,缺省值错误,或精度不够。

11.13 【建议】当心变量发生上溢或者下溢,数组的下标越界。

11.14 【建议】当心未编写错误处理程序,当心错误处理程序本身有误。

11.15 【建议】当心文件i/o有错误。避免编写技巧性很高的代码。

11.16 【建议】如果原有代码质量比较好,尽量复用它。

11.17 【建议】如果可能的话,使用PC-LintLogiScope等工具进行代码审查。



0 0