<<高质量c++c编程指南>>读书笔记2

第九章 类的构造函数、析构函数与赋值函数
9.1 示例:类String的构造函数
String::String( const char *str )
{
    if( NULL == str )
    {
        m_data = new char[ 1 ];
        *m_date = '\0';
    }
    else
    {
        int length = strlen( str );
        m_data = new char[ length + 1 ];
        strcpy( m_data, str );
    }
}

9.2 不要轻视拷贝构造函数与赋值函数
    由于并非所有的对象都会使用拷贝构造函数和赋值函数,程序员可能对这两个函数有些轻视。
(1) 如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省值的函数。倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。
(2) 拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。
String a( "hello" );
String b( "world" );
String c = a; // 调用了拷贝构造函数,最好写成c( a );
       c = b; // 调用了赋值函数
第三个语句的风格较差,宜改写成String c( a )以区别于第四个语句

9.3 类String的拷贝构造函数与赋值函数
// 拷贝构造函数
String::String( const String &other )
{
    int length = strlen( other.m_data );
    m_data = new char[ length + 1];
    strcpy( m_data, other.m_data );
}
// 赋值函数
String & String::operator=( const String &other )
{
    // 检查自赋值
    if( this == &other )
    {
        return *this;
    }

    // 释放原有的内存资源
    delete[] m_data;

    // 分配新的内存资源,并复制内容
    int length = strlen( other.m_data );
    m_data = new char[ length + 1];
    strcpy( m_data, other.m_data );

    // 返回本对象的引用
    return *this;
}
    类String拷贝构造函数与普通构造函数的区别是:在函数入口处无需与NULL进行比较,这是因为“引用”不可能为NULL,而“指针”可以为NULL
    注意函数strlen返回的是有效字符串长度,不保护结束符'\0'。函数strcpy则连'\0'一起复制
    返回本对象的引用,目的是为了实现像a = b = c注意的链式表达。不能写成return other,因为不知道参数other的生命期。有可能other是个临时对象,在赋值结束后它马上消失,那么return other返回的将是垃圾

9.4 偷懒的办法处理拷贝构造函数与赋值函数
    只需将拷贝构造函数与赋值函数声明为私有函数,不用编写代码,例如:
class A
{
private:
    A( const A &a );              // 私有的拷贝构造函数
    A & operator =( const A &a ); // 私有的赋值函数
};
如果试图编写:
A b( a ); // 调用了私有的拷贝构造函数
b = a;    // 调用了私有的赋值函数
编译器将指出错误,因为外界不可以操作A的私有函数

9.5 如何在派生类中实现类的基本函数
    基类的构造函数、析构函数、赋值函数都不能被派生类继承。如果类之间存在继承关系,在编写上述基本函数时应注意:
a、派生类的构造函数应在其初始化表里调用基类的构造函数
b、基类与派生类的析构函数应该为虚(即加virtual关键字)
c、在编写派生类的赋值函数时,不要忘记对基类的数据成员重新赋值
class Base
{
public:
    ...
    Base & operator =( const Base &other ); // 类Base的赋值函数
private:
    int m_i, m_j, m_k;
};
class Derived : public Base
{
public:
    ...
    Derived & operator = ( const Derived &other ); // 类Derived的赋值函数
private:
    int m_x, m_y, m_z;
};

Derived & Derived::operator = ( const Derived &other )
{
    // 检查自赋值
    if( this == &other )
    {
        return *this;
    }

    // 对基类的数据成员重新赋值
    Base::operator = ( other ); // 因为不能直接操作私有数据成员

    // 对派生类的数据成员赋值
    m_x = other.m_x;
    m_y = other.m_y;
    m_z = other.m_z;

    // 返回本对象的引用
    return *this;
}

 

第十章 类的继承与组合
10.1 继承
(1) 如果类A和类B毫不相关,不可以为了使B的功能更多些而让B继承A的功能和属性
(2) 若在逻辑上B市A的“一种”( a kind of ),则允许B继承A的功能和属性。例如男人是人的一种,男孩是男人的一种。那么累Man可以从类Human派生,类Boy可以从类Man派生

10.2 组合
若在逻辑上A是B的“一部分”( a part of ),则不允许B从A派生,而是要用A和其他东西组合出B,例如眼睛(Eye)、鼻子(Nose)、口(Mouth)、耳朵(Ear)是头(Head)的一部分,所以类Head应该有类Eye、Nose、Mouth、Ear组合而成,不是派生而成
class Eye
{
public:
    void Look( void );
};
class Nose
{
public:
    void Smell( void );
};
class Mouth
{
public:
    void Eat( void );
};
class Ear
{
public:
    void Listen( void );
};

// 正确的Head设计,虽然代码冗长
class Head
{
public:
    void Look( void )  { m_eye.Look(); }
    void Smell( void ) { m_nose.Smell(); }
    void Eat( void )   { m_mouth.Eat(); }
    void Listen( void ){ m_ear.Listen(); }
private:
    Eye m_eye;
    Nose m_nose;
    Mouth m_mouth;
    Ear m_ear;
};

 

第十一章 其他编程经验
11.1 使用const提高函数的健壮性
const更大的魅力是它可以修饰函数的参数、返回值,甚至函数的定义提
(1) 用const修饰函数的参数
    如果参数做输出用,不论它是什么数据类型,也不论他采用“指针传递”还是“引用传递”,都不能加const修饰,否则该参数将失去输出功能
    const只能修饰输入参数:
a、如果输出参数采用“指针传递”,那么假const修饰可以防止意外地改动该指针,起到保护作用
b、如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无须保护,所以不要加const修饰,例如不要将函数void Func1( int x )写成void Func1( const int x ),同理不要将函数void Func2( A a )写成void Func2( const A a )。其中A为用户自定义数据类型
c、void Func2( A a )效率比较低可以使用A &a,但是注意要加上const;但如果将void Func1( int x )写成void Func1( const int &x ),完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当

(2) 用const修饰函数的返回值
a、如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给const修饰的同类型指针
例如函数:const char *GetString( void );
如下语句将会出现编译错误
char *str = GetString();
正确的用法是
const char *str = GetString();
b、如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const修饰没有任何价值
例如不要把函数int GetInt( void )写成const int GetInt( void )
同理不要把函数A GetA( void )写成const A GetA( void ),其中A为用户自定义数据类型
c、如果返回值不是内部数据类型,将函数A GetA( void )改写成const A & GetA( void )的确能提高效率。但此时千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错

(3) const成员函数
    任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其他非const成员函数,编译器将指出错误,这无疑会提高成员的健壮性
class Stack
{
public:
    void Push( int elem );
    int Pop( void );
    int GetCount( void ) const; // const成员函数
private:
    int m_num;
    int m_data[ 100 ];
};

int Stack::GetCount( void ) const
{
    ++m_num; // 编译错误,企图修改数据成员m_num
    Pop();   // 编译错误,企图调用非const函数
    return m_num;
}
const成员函数的声明看起来很怪;const关键字只能放在函数声明的尾部,大概是因为其他地方都已经被占用了

11.2 提高程序的效率
    程序的时间效率是指运行速度,空间效率是指程序占用内存或者外存的状况
(1) 不要一味地追求程序的效率,应当在满足正确性、可靠性、健壮性、可读性等质量因素的前提下,设法提高程序的效率
(2) 在优化程序的效率时,应当先找出限制效率的“瓶颈”,不要再无关紧要之处优化
(3) 先优化数据结构和算法,再优化执行代码 
(4) 不要追求紧凑的代码,因为紧凑的代码并不能产生高效的机器码

11.3 一些有益的建议
(1) 当心数据类型转换发生错误。尽量使用显示的数据结构类型转换(让人们知道发生了什么事),避免让编译器悄悄地进行隐式的数据类型转换
(2) 避免编写技术性很高的代码
(3) 不要设计面面俱到、非常灵活的数据结构
(4) 如果原有的代码直来那个比较好,尽量复用他。但是不要修补很差劲的代码,应当重新编写
(5) 尽量使用标准库函数,不要“发明”已经存在的库函数
(6) 尽量不要使用与具体硬件或软件环境关系密切的变量
(7) 把编译器的选项设置为最严格状态
(8) 如果可能的话,使用PC-Lint、LogiScope等工具进行代码审查

 

第十二章 补充
12.1 仔细设计结构中玉萨怒的布局与排列顺序,使结构容易理解、节省占用空间,并减少引起误用现象
示例:如下结构中的位域排列,将占较大空间,可读性也稍差
typedef struct EXAMPLE_STRU
{
    unsigned int valid : 1;
    PERSON person;
    unsigned int set_flg : 1;
}EXAMPLE;
若改成如下形式,不仅可节省1字节空间,可读性也变好了
typedef struct EXAMPLE_STRU
{
    unsigned int valid : 1;
    unsigned int set_flg : 1;
    PERSON person;
}EXAMPLE;

12.2 结构的设计要尽量考虑向前兼容和以后的版本升级,并未某些未来可能的应用保留余地(如预留一些空间等)
     说明:软件向前兼容的特性,是软件产品是否成功的重要标志之一。如果要想使产品具有较好的前向兼容,那么在产品设计之初就应为以后版本升级保留一定余地,并且在产品升级时必须考虑前一版本的各种特性。

12.3 对自定义数据类型进行恰当命名,使它成为自描述性的,以提高代码可读性。注意其命名方式在同一产品中的统一。
     说明:使用自定义类型,可以弥补编程语言提供类型少、信息量不足的缺点,并能使程序清晰、简洁。
下面的声明可使数据类型的使用简洁、明了
typedef unsigned char  BYTE;
typedef unsigned short WORD;
typedef unsigned int   DWORD;
下面的声明可使数据类型具有更丰富的含义:
typedef float DISTANCE;
typedef float SCORE;

12.4 函数、过程
(1) 对所调用函数的错误返回码要仔细、全面地处理

(2) 编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护
    说明:若对所使用的全局变量不加以保护,则此函数就不具有课重入性,即当多个进行调用此函数时,很有可能使有关全局变量变为不可知状态
示例:假设Exam是int型全局变量,函数Squre_Exam返回Exam平方值。那么如下函数不具有可重入性
unsigned int example( int para )
{
    unsigned int temp;

    Exam = para; // (**)
    temp = Square_Exam();
   
    return temp;
}
此函数若被多个进程调用的话,其结果可能是位置的,因为当(**)语句刚执行完毕后,另外一个使用本函数的进程可能正好被激活,那么当激活的进程执行到此函数时,将使Exam赋与另一个不同的para值,所以当控制重新回到"temp = Square_Exam();"后,计算出的temp很可能不是预想中的结果。此函数应如下改进:
unsigned int example( int para )
{
    unsigned int temp;

    [申请信号量操作]      // 若申请不到“信号量”,说明另外的进程正处理给Exam赋值并计算其
    Exam = para; // (**)  // 平方过程中(即正在使用此信号),本进程必须等待其释放信号后,才
    temp = Square_Exam(); // 可继续执行,但其他进程必须等待本进程释放信号量后,才能在使用
    [释放信号量操作]      // 本信号
   
    return temp;
}

(3) 在同一项目组应明确规定对接口函数参数的合法性检查应有函数逇调用者负责还是接口函数本身负责,缺省是由函数调用者负责
    说明:对于模块接口函数的参数逇合法性检查这一问题,往往有两个极端现象,即:要么是调用者和被调用者对参数不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。

(4) 防止就爱那个函数的参数作为工作便利
    说明:将函数逇参数作为工作便利,有可能错误地改变参数内容,所以很危险。对必须改变的参数,最好先用局部变量代之,最后再讲该局部变量的内容赋给该参数
示例:下函数的实现不太好
void sum_date( unsigned int num, int *data, int *sum )
{
    unsigned int count;
    
    *sum = 0;
    for ( count = 0; count < num; count++ )
    {
        *sum += data[ count ]; // sum成了工作变量,很不好
    }
}
若改为如下,则更好些:
void sum_date( unsigned int num, int *data, int *sum )
{
    unsigned int count;
    int sum_temp;
    
    sum_temp = 0;
    for ( count = 0; count < num; count++ )
    {
        sum_temp += data[ count ]; // sum成了工作变量,很不好
    }

    *sum = sum_temp;
}

(5) 函数的规模尽量限制在200行以内
    说明:不包括注释和空格行
(6) 一个那还俗仅完成一个功能
(7) 不要设计多用途面面俱到的函数
    说明:多功能集于一身的函数,很可能使函数的理解、测试、维护等变得困难

(8) 函数的功能应该是可以预测的,也就是只要输入数据相同就应产生同样的输出 
    说明:带有内部“存储器”的函数功能可能是不可测的,因为他的输出可能取决于内部存储器(如某标志)的状态。这样的函数既不易于理解有不利用测试和维护。在C/C++语言中,函数的static局部变量是函数的内部存储器,有可能使函数的功能不可预测,然而,当某函数的返回值i为指针类型是,则必须是static的局部变量的地址作为返回值,若为auto类,则返回为指针
示例:如下函数,其返回值是不可预测的
unsigned int integer_sum( unsigned int base )
{
    unsigned int index;
    static unsigned int sum = 0; // 注意,是static类型的,若改为auto类型,则函数即变为可预测

    for ( index = 1; index <= base; index++ )
    {
        sum += index;
    }

    return sum;
}

(9) 避免设计多参数函数,不使用的参数从接口中去掉
    说明:目的减少函数间接口的复杂度

(10) 非调度函数应减少或防止控制参数,尽量只是用数据参数
     说明:本建议目的是防止函数间的控制耦合。调度函数是指根据输入的消息类型或控制命令,来启动相应的功能实体(即函数或过程),而本身并不完成具体功能。控制参数是指改变函数功能行为的参数,即函数要根据此参数来决定具体怎样工作。非调度函数的控制参数增加了函数间的控制耦合,很可能使函数间的耦合度增大,并使函数的功能不唯一。
示例:如下函数构造不太合理
int add_sub( int a, int b, unsigned char add_sub_flg )
{
    if ( INTEGER_ADD == add_sub_flg )
    {
        return ( a + b );
    }
    else
    {
        return ( a - b );
    }
}
不如分为如下两个函数清晰:
int add( int a, int b )
{
    return ( a + b );
}
int sub( int a, int b )
{
    return ( a - b );
}

(11) 检查函数所有参数输入的有效性
(12) 检查函数所有非参数输入的有效性,如数据文件、公共变量等
     说明:函数的输入主要有两种:一种是参数输入;另一种是全局变量、数据文件的输入,即非参数输入。函数在使用输入之前,应进行必要的检查。
(13) 使用动宾词组为执行某操作的函数命名。如果是OOP方法,可以只有动词(名词是对象本身)
示例:参照如下方式命名函数
void print_record( unsigned int rec_ind );
int input_record( void );

(14) 函数的返回值清楚、明了,让使用者不容易忽视错误情况
     说明:函数的美中出错返回值的意义要清晰、明了、准确,防止使用者误用、理解错误或忽视错误返回码

(15) 防止把没有关联的语句放到一个函数中
     说明:防止函数或过程内出现随机内聚。随机内聚是指将没有关联或关联很弱的语句放到同一个函数或过程中。随机内聚给函数火锅城的维护、测试及以后的升级造成了不变,同事也使函数或过程的功能不明确。使用随机内聚函数,常常容易出现在一种应用场合需要改进此函数,而另一种应用场合又不允许这种改进,从而陷入困境。
     在编程时,经常遇到在不同韩式中使用相同的代码,许多开发人员都愿把这些代码提出来,并构成一个新函数。若这些代码关联较大并且是完成一个功能的,那么这种构造是合理的,否则这种构造将产生随机内聚的函数
示例:
void Init_Var( void )
{
    Rect.length = 0;
    Rect.width = 0;

    Point.x = 10;
    Point.y = 10;
}
举行的长、宽和点的左边基本没有任何关系,故以上函数是随机内聚
应如下分为两个函数:
void Init_Rect( void )
{
    Rect.length = 0;
    Rect.width = 0;
}
void Init_Point( void )
{
    Point.x = 10;
    Point.y = 10;
}

(16) 如果多段代码重复做同一件事情,那么在函数的划分上可能存在问题
     说明:若此段代码个语句之间有实质性关联并且是完成同一件功能的,那么可考虑把此段代码构成一个新的函数
(15) 功能不明确较小的函数, 特别是仅有一个上级函数调用它时,应考虑把它合并到商机函数中,而不必单独存在
     说明:模块中函数划分的过多,一般会使函数间的接口变得复杂。所以过小的函数,特别是闪入很低或功能不明确的函数,不值得单独存在
(16) 仔细分析模块的功能及性能需求,并进一步细分,同时若有必要画出有关数据流图,据此来进行木的函数划分与组织
     说明:函数的划分与组织是模块的实现过程中很关键的步骤,如何划分出合理的函数结构,关系到模块的最终效率和可维护性、可测性等。格局模块的功能图或/数据流图映射出函数结构是常用的方法之一

(17) 改进模块中的函数结构,降低函数间的耦合度,并提供函数的独立性以及代码可读性、效率和可维护性。优化函数结构时,要遵守以下原则:
a、不能影响模块功能的实现
b、仔细考察模块或函数出错处理及模块的性能要求并进行完善
c、通过分级或合并函数来改进软件结构
d、考察函数的规模,过大的要进行分解
e、降低函数间接口的复杂度
f、不同层次的函数调用要有较合理的扇入、扇出
g、函数功能应可预测
h、提高函数内聚(单一功能的函数内聚最高)
说明:对初步划分后的函数结构应进行改进、优化,使之更为合理

(18) 避免使用BOOL参数
     说明:原因有而,其一是BOOL参数值无意义,TRUE/FALSE的含义是非常模糊的,在调用时很难知道该蚕食到底传达的是什么意思;其二是BOOL参数值不利于扩充。还有NULL夜市一个无意义的单词。

(19) 当一个过程(函数)中对较长变量(一般是结构的成员)有较多引用时,可以用一个意义相当的宏代替
     说明:这样可以增加编程效率和程序的可读性
示例:在某过程中较多引用TheReceiveBuffer[ FirstSocker ].buDataPtr
则可以通过以下宏定义来替换:
#define pSOCKDATA TheReceiveBuffer[ FirstSocker ].buDataPtr

12.5 可测性
(1) 在同一项目组或产品组中,要有一套同一的为集成测试与系统联调准备的调测开关及相应的打印函数,并且要有详细的说明
(2) 编程的同时要为单元测试选择恰当的测试点,并仔细构造测试代码、测试用例,同时给出明确的注释说明。测试代码部分应作为(模块中的)一个子模块,以网编测试代码在模块中的安装于卸载(通过调测开关)
(3) 在进行集成测试/系统联调之前,要构造好测试环境、测试项目及测试用例,同时仔细分析并优化测试用例,以提高测试效率
    说明:好的测试用例应尽可能模拟出程序所遇到的边界值、各种复杂环境及一些极端情况等
(4) 使用断言来发现软件问题,提高代码可测性
    说明:
(5) 使用断言来发现软件问题,提高代码可测性
    说明:断言是对某种假设条件进行检查(可理解为若条件成立则无动作,否则应报告),他可以快速发现并定位软件问题,同时对系统错误进行自动报警。断言可以对在系统中隐藏很深,用其他手段极难发现的问题进行定位,从而缩短软件问题定位时间,提高系统的可测性。实际应用时,可根据具体情况灵活地设计断言。
示例:下面是C语言中的一个断言,用宏来设计的(其中NULL为0L)
#ifdef _EXAM_ASSERT_TEST_ // 若使用断言测试

void exam_assert( char *file_numa, unsigned int line_no )
{
    printf( "\n[EXAM]Assert failed:%s, line %u\n", file_name, line_no );
    abort();
}

#define EXAM_ASSERT( condition )
{
    if ( condition ) // 若条件成立,则无动作
    {
        NULL;
    }
    else
    {
        exam_assert( __FILE__, __LINE__)
    }
}
#else // 若不使用断言测试

#define EXAM_ASSERT( condition ) NULL

#endif /* end of ASSERT */
(6) 不能用断言来检查最终产品肯定会出现且必须处理的错误情况
    说明:断言是用来处理不应该发生的错误情况的,对于可能会发生的且必须处理的情况要写防错程序,而不是断言。如某模块收到其他模块或链路上的消息后,要对消息的合理性进行检查,此过程为正常的错误检查,不能用断言来实现

(7) 对较复杂的断言加上明确的注释
    说明:为复杂的断言加注释,可澄清断言含义并减少不必要的误用

(8) 用断言保证没有定义的特性或功能不被使用
示例:假设某通信模块在设计时,准备提供“无连接”和“连接”这两种业务。但当前的版本中仅实现了“无连接”业务,且在此版本的正式发行版中,用户(上层模块)不用产生“连接”业务的请求,那么在测试时可用断言检查用户是否使用“连接”业务。如下:
#define EXAM_CONNECTIONLESS 0 // 无连接业务
#define EXAM_CONNECTION     1 // 连接业务

int msg_process( EXAM_MESSAGE *msg )
{
    unsigned char service;

    EXAM_ASSERT( NULL != msg );

    service = get_msg_service_class( msg );

    EXAM_ASSERT( service != EXAM_CONNECTION); // 假设不使用连接业务
....
}

(8) 正式软件产品中应把断言及其他调测代码去掉(即把有关的调测开关关掉)
    说明:加快软件运行速度

(9) 在编写代码之前,应先设计好程序调试与测试的方法和手段,并设计好各种调测开关及相应测试代码如打印函数等
    说明:程序的调试与测试是软件生命周期中很重要的一个阶段,如何对软件进行较全面、高效的测试并尽可能低找出软件中的错误就成为很关键的问题。因此在编写源代码之前,除了要有一套比较完善的测试计划外,还应设计出一系列代码测试手段,为单元测试、集成测试及系统联调提供方便

(10) 调测开关应分为不同级别和类型
     说明:调测开关的设置及分类应从以下几方面考虑:针对模块或系统某部分代码的调测;针对模块或系统某功能的调测;处于某种其他目的,如对性能、容量等的测试、这样做便于软件功能的调测,并且便于模块的单元测试、系统联调等

12.6 程序效率
(1) 编程时要经常注意代码的效率
    说明:代码效率分为全局效率、局部效率、时间效率及空间效率。

(2) 循环体内工作量最小化
    说明:应仔细考虑循环体内的语句是否可以放在循环体之外,是循环体内工作量最小,从而提高程序的时间效率

(3) 对模块中函数的划分及组织方式进行分析、优化,改进模块中函数的组织结构,提高程序效率
    说明:软件系统的效率主要与算法、处理任务方式、系统功能及函数结构有很大关系,尽在代码上下功夫一般不能解决根本问题
(4) 编程时,要随时留心代码效率;优化代码时,要考虑周全

(5) 要仔细的构造或直接用汇编编写调用频繁或性能要求极高的函数
    说明:只有对编译系统产生机器码的方式以及硬件系统较为熟悉时,才可以使用汇编嵌入方式。嵌入汇编可提高时间及空间效率,但也存在一定风险

(6) 在多重循环中,应将最忙的循环放在最外层
    说明:减少cpu切入循环层的次数

(7) 避免循环体内含判断语句,应将循环语句至于判断语句的代码块之中
    说明:目的是减少判断次数。循环体重的判断语句是否可以移到循环体外,要视程序的具体情况而言,一般情况,与循环变量无关的判断语句可以移到循环体外,而有关的则不可以。

(8) 尽量用乘法或其他方法代替除法,特别是浮点运算中的触发
    说明:浮点运算触发要占用较多cpu资源
示例:如下表达式运算可能要占较多cpu资源
#define PAI 3.1416
radius = circle_length / ( 2 * PAI );

应如下把浮点除法改为浮点乘法
#define PAI_RECIPROCAL ( 1 / 3.1416 ); // 编译器编译时,将发生具体浮点数
radius = circle_length * PAI_RECIPROCAL / 2;

12.7 代码编辑、编译、审查
(1) 在产品软件(项目组)中,要统一编译开关选项
(2) 通过代码走读及审查方式对代码进行检查
    说明:代码走读主要是对程序的编译风格如注释、命名等以及编程时易出错的内容进行检查,可由开发人员自己或开发人员交叉的方式进行;代码审查主要是对程序实现的功能及程序的稳定性、安全性、可靠性等进行检查及评审,可通过自审、交叉审核或指定部门抽查等方式进行

12.8 代码测试、维护
(1) 单元测试要求至少达到语句覆盖
(2) 单元测试开始要跟踪每一条语句,并观察数据流及变量的变化
(3) 清理、整理或优化后的代码要经过审查及测试
(4) 代码版本升级要经过严格测试
(5) 使用工具软件对代码版本进行维护
(6) 正式版本上软件的任何修改都应有详细的文档记录
(7) 关键的代码在汇编级跟踪
(8) 仔细设计并分析测试用例,使测试用例覆盖尽可能多的情况,以提高测试用例的效率
(9) 尽可能模拟出程序的各种出错情况,对出错处理代码进行充分的测试
(10) 仔细测试代码处理数据、变量的边界情况
(11) 保留测试信息,以便分析、总结经验及进行更充分的测试
(12) 不应通过“试”来解决问题,应寻找问题的根本原因
(13) 对自动消失的错误进行分析,搞清楚错误时如何消失的
(14) 修改错误不仅要治标,更要治本
(15) 测试时应设法使很少发生的事件经常发生
(16) 明确模块或函数处理哪些事件,并使他们经常发生
(17) 坚持在编码阶段就对代码进行彻底的单元测试,不要等以后的测试工作来发现问题
(18) 去除代码运行的随机性(如去掉误用的数据、代码及尽可能防止并注意函数中的“内部寄存器”等),让函数运行的结果可预测,并使出现的错误可再现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值