zoukankan      html  css  js  c++  java
  • 改善c++程序的150个建议(读后总结)-------0-9

    0. 不要让main 函数返回 void
    入口函数main()返回类型应该为 int, 即程序结束时return 0 表示程序正常返回,函数结束时 return -1 值表示程序异常返回, 如果不显式写出 return 语句,编译器会隐式的加上 return 0(并不要以为main函数可以无返回值),在定义main函数时为了不产生误解,应显式加上return 语句。
    注意在老版本中的C++中因为只有 int一种类型所以可以不显式写出main函数的返回值,其会默认main函数返回 int类型,但是现在的大多数c++编译器都不支持 main函数默认返回 int类型,在定义main函数时必须显式的给出其返回值为 int类型。
    无论怎样,c++标准都没有定义 void main()这种代码形式,main函数必须返回 int类型

    //标准的main函数定义
    int main()
    {
    	return 0;
    }
    

    1.区分0的4种面孔
    (1)int 类型的0,其在32位系统的内存中占4个字节。
    (2)指针类型的0(NULL),其也占4个字节
    注意:整形整型常量0可以被当成空地址使用,而其他整型常量不能当做地址使用。int *p=0是合法的,而int *p =1是不合法的。
    为了增加代码的可读性,应该把NULL当空地址使用,而不是用0,以免误认为整型常量可以当做地址使用。
    NULL的宏定义为# define NULL 0,所以NULL可以当做常量0使用,但是为了代码的可读性我们通常不这么做。
    (3)字符串结束标志 ‘’
    字符串常量的末尾都会默认加一个字符串结束符 ‘’,
    (4)逻辑FALSE/ false
    FALSE和TRUE是在windef.h中定义的宏,false和true是c++中的关键字。前者是int类型占4个字节
    而后者是bool类型占1个字节。一般都是使用布尔类型的false

    2. 避免由那些运算符引起的混乱

    if(a=0)
    {
    	//code
    }
    

    程序员的本意是if(a==0),但是如果if语句写成这样则c++会先先把0赋值给a后判断a为0,其if代码块永远都不会被执行,而这种写法没有语法错误,c++不会报错而且这个bug很难察觉到。
    为了避免这种情况的发生可以把a与0交换位置
    即可以写成:

    if(0==a)
    {
    	//code
    }
    

    这样如果写成if(0=a)其会报错,我们可以找到其将其更正,比第一种写法找不到错误要好多了。
    **3.对表达式的计算顺序不要想当然 **
    (1)运算符的优先级问题
    因为c++中的运算符较多且具有复杂的优先级关系,在使用较长表达式使要使用()来表明自己的意图,能增加代码的可读性也不易出现意想不到的错误。

    无论是函数参数还是操作符绝大多数语言都不能确定哪一个参数或哪一个操作数(不包括&& ,||, ?:和,)先被执行
    (2)函数参数的评估求值顺序

    int a();
    int b();
    int nasa(int,int);
    nasa(a(),b());
    

    c语言并不能确定nasn函数的参数是先执行a()还是先执行b(),如果a函数与b函数之间没有任何联系谁也影响不到谁,或者编程者的意图与先调用哪个函数没有关系则不用考虑这个问题。但是如果编程者的编程意图与其函数的执行顺序有关则应该写成

    int n=a();    //想要先执行的函数
    nasa(n,b());
    

    (3)操作数的评估求值顺序

    a=p()+q()*w();
    

    同样c语言并不能保证是先执行p()还是先执行q(),还是先执行w()。
    如果想要编程意图想要先执行p(),在执行q(),最后执行w()应写成

    int x=p();
    int y=q();
    a=x+y+w();
    

    但有些操作符的操作数具有特定的执行顺序,&&,||,?:和 ,
    例如:a && b
    如果a为假则直接返回假,b根本不会执行。(&&和 ||的短路算法特性)
    a ?b:c
    a会先执行然后执行b与c中的一个,返回值为b与c中被执行的那个值

    总结(2)和(3):在编程时不要依靠函数参数和操作数的评估顺序来实现自己的编程意图的到自己想要的结果,这样很容易出错,因为往往把控不好其评估顺序(程序并没有按照自己的意向顺序执行)从而得不到自己想要的结果。

    4.小心宏#define使用中的陷阱
    #define宏定义只是单纯的进行符号替换,没有任何检查因此极易出错
    (1)使用宏定义时要是用完备的符号

    #define add(a,b)     a+b 
    int n=add(1,5)*add(4,3) 
    

    n结果为1+54+3=24
    但是编程者的本意为(1+5)
    (4+3)
    所以宏定义单纯的符号替换会带来很大的麻烦,要想解决这种问题就要在宏定义时使用完备的括号

    #define add(a,b)  ((a)+(b))
    

    (2)使用宏时不要使用需要变化的参数

    #define add(a)  ((a)+(a))
    int n=5;
    int b=add(n++);
    

    其计算为 b=((n++)+(n++))
    n的结果为7
    b的结果为11
    而编程者的意图为 b=(n+n) ; n=n+1 ;而宏为简单的符号替换,所以不要使用宏时变化的参数。
    由此也可以看出宏定义并不能和定义函数等同,因为函数参数可以使变化的,并不向宏定义那样是简单的符号替换,add函数写成函数的形式为

    int add(int,int);
    int main()
    {
    	int a=5;
    	int b=add(a++);
    }
    int add(int n)
    {
    	return a+a;
    }
    

    a的结果为6
    b的结果为10
    其计算过程为 b=a+a; a=a+1;
    (3)用大括号把含有多条语句的宏定义括起来
    如果一个宏的定义有多条语句,而没有用大括号把语句括起来在用宏定义时只会执行第一条语句。

    5. 不要忘记指针变量的初始化
    在定义指针变量的同时要对其进行初始化,因为使用没有 初始化的指针是一件很危险的事,未初始化的指针会随机指向程序中的一块存储单元,这是对局部指针变量而言,对于全局变量指针程序会自动对其进行初始化。
    所以在使用局部指针变量时一定要对其进行初始化。

    6. 明晰逗号分隔符的奇怪之处
    表达式1,表达式2,表达式3…
    逗号运算符其操作数执行顺序为先左后右,整体返回值为最右边表达式的值。
    注意并不是所有有逗号的地方都是逗号表达式,如函数参数列表用号分割就不是逗号表达式。

    7. 时刻提防内存溢出
    内存溢出:在计算机内存中,当表达的数据超过计算机为该类型数据分配的空间时就会产生内存溢出。
    如在使用strcpy函数时

    #define  MAX_NUM   24
    void nasa(int a[])
    {
    	int b[MAX_NUM];
    	strcpy(b,a);
    }
    

    strcpy是吧a中的数据复制到b中,知道遇到 '’字符串结束标志(‘’也被复制到b),但如果其在填满24个字节后还没有遇见‘’,其会把缓冲区后面的数据覆盖掉,如果恰好把函数nasa的返回地址数据覆盖掉,则攻击者会利用这一点让程序转入其危险的 ‘返回地址’。

    为了避免落入攻击者的陷阱,需要检查数据是否大于内存缓冲区的最大空间

    #define  MAX_NUM   24
    void nasa(int a[],int num_a)
    {
    	int b[MAX_NUM];
    	if(num_a<=MAX_NUM)
    		strcpy(b,a);
    	else
    		b[num_a]='';
    }
    

    当数据长度不大于内存缓冲区的最大长度时进行复制,当内存缓冲
    区溢出时,向其中写入常量会使代码放生错误,终止运行这样就比落入攻击者的陷阱要好得多。

    访问边界数据也会发生缓冲区溢出,而且很难发现因为它是合法的,c++语言并不能检测出来向数组访问越界这种问题。

    8. 拒绝晦涩难懂的函数指针

     void (*p[10])  (void(*)( ));
    

    像这种包含多个符号的定义很难理解其含义,可读性极低,为了使其更易理解应用typedef方法
    上述声明其实是一个函数指针数组,且函数的返回类型为void
    用typedef来表示
    9.防止重复包含头文件
    如果一个.cpp文件中包含了重复的头文件,则会报错。
    为了防止包含重复的头文件有两种处理方式
    (1)

    #ifndef __SOMEFILE_H__
    #define __SOMEFILE_H__
    ... ...//声明,定义语句
    #endif
    

    其是c++标准支持的,所以用任何编译器都可以使用这种方式。

    这种方式不仅可以防止包含多个重复的头文件,也能保证多个内容相同的头文件不会被同时包含。但是如果有两个文件中出现相同的宏名,会发生明明头文件被包含了,但显示头文件未声明。(有的编译器还会把后包含的文件中的宏名的定义参照前一个包含的文件中的宏定义,即使用重复定义的宏定义都按照最先包含的头文件中的宏定义)
    为了避免这种错误不要在不同的头文件中定义相同的宏名,这样在.cpp文件中使用宏的时候就不会产生歧义了。
    而且这种方式其需要打开头文件查看是否有重复定义,所以在编译较大文件时时间较长。
    (2)

    #pragma once
    ... ...//声明,定义语句
    

    这种方式是由编译器提供的,所以有的编译器支持有的支持则不能使用(兼容性不好)
    其可以保证不会包含重复的文件,而且其包含的不同的文件中可以使用相同的宏名,效率更高(不需要打开文件去检查是否有重复定义),但这样其在.cpp文件中使用宏的时候其也是会把重复定义的宏按照最先包含的头文件中的宏定义使用。

    //所以在不同的头文件中不要使用重复的宏名
    
  • 相关阅读:
    逻辑指的是思维的规律和规则,是对思维过程的抽象。
    逻辑
    什么是概念,判断和推理
    逻辑思维
    console.log 用法
    console.log()的作用是什么
    vue-cli创建的项目中引入第三方库报错 'caller', 'calle', and 'arguments' properties may not be...
    element-ui中upload组件如何传递文件及其他参数
    vueThink权限配置
    vue2.0集成百度UE编辑器,上传图片报错!!!
  • 原文地址:https://www.cnblogs.com/revercc/p/13287095.html
Copyright © 2011-2022 走看看