当前位置:首页 » 《随便一记》 » 正文

C/C++编程中容易犯的10种初级编程错误

29 人参与  2022年09月23日 08:14  分类 : 《随便一记》  评论

点击全文阅读


?个人主页:个人主页

?系列专栏:C/C++基础与进阶

?推荐一款模拟面试、刷题神器,从基础到大厂面试题?点击跳转刷题网站进行注册学习

目录

1、有些关键字不用写在cpp中

2、函数参数的默认值写到函数实现中了

3、在编写类时,在类的结尾处忘记添加";"分号了

4、只添加了函数声明,没有函数实现

5、cpp文件忘记添加到工程中,导致没有生成供链接使用的obj文件

6、函数中返回了一个局部变量的地址或者引用

7、忘记将父类中的接口声明virtual函数,导致多态没有生效

8、该使用双指针的地方,却使用了单指针

9、发布exe程序时,忘记将exe依赖的C运行时库或MFC库带上了

10、应该使用深拷贝,却用了浅拷贝


       IT公司每年都会有一定的人员流动,相应地也会招一些应届生补充进来,指导应届生已经成为老员工的必修课了。平日里我们会经常帮新人看代码中的问题,在此过程中发现了C/C++新手容易犯的一些编程错误,在此简单的总结一下,给新人们一个参考。 

C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931

VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585

1、有些关键字不用写在cpp中

       对于C++类,一些关键字只要写在.h中就好,cpp中就不用再加上了,比如virtual、static等关键字,如果再cpp中多写,编译器会报错。比如如下的虚接口与静态成员变量的定义,只要在头文件中声明就可以了。

class shape{    virtual Draw();    //...    static int nLevel;}

2、函数参数的默认值写到函数实现中了

        带有参数默认值的函数,默认值是加在函数声明处的,函数实现处的参数是不需要带上的。为了方便查看代码,在函数实现处的参数中,将默认值注释起来。

       正确的做法是,头文件中有默认值:

BOOL CreateConf( const CString& strConfName, const BOOL bAudio = FALSE );

      在函数实现处的参数中不用添加默认值:

BOOL CreateConf( const CString& strConfName, const BOOL bAudio/* = FALSE*/ );{    // ......}

3、在编写类时,在类的结尾处忘记添加";"分号了

        在类的结尾处忘记添加分号,编译会报错。新人们有可能找了半天,也没找出引起编译错误的原因。其实很简单,在类的结尾处忘记添加分号了。

class Shape{    // ...};

4、只添加了函数声明,没有函数实现

       在添加类的函数时,只在类的头文件中添加了函数声明,但在cpp中却没有添加函数的实现。如果其他地方调用到该函数,在编译链接的时候会报 unresolved external symbol错误。因为没有实现,所有没有供链接使用的obj文件。

在头文件添加的函数声明,在编译时会用到声明;在cpp文件添加的函数实现,会编译成函数符号,在链接的时候会用到。

5、cpp文件忘记添加到工程中,导致没有生成供链接使用的obj文件

       在添加C++类时,我们一般会添加一个.h头文件和一个对应的.cpp源文件。如果忘记把.cpp文件添加到工程中了,即没有参与编译,没有生成供链接使用的obj文件。如果有代码调用到该C++类的接口,则在编译链接的时候会报 unresolved external symbol错误,即链接不到该C++类对应的接口。这个问题也经常会遇到。

6、函数中返回了一个局部变量的地址或者引用

       在函数中返回了一个局部变量的地址或者引用,而这个局部变量在函数结束时其生命周期就结束了,内存就被释放了。当外部访问到传出的地址或引用时,就访问了已经释放的内存,就会触发内存访问违例的异常,因为该变量的内存已经释放了。比如如下的错误代码:

char* GetResult(){    char chResult[100] = { 0 };     // ......     return chResult;}

这是编程新手容易犯的错误,主要是对变量的声明周期理解的不到位导致的。 

7、忘记将父类中的接口声明virtual函数,导致多态没有生效

       代码中本来要借助于C++多态的虚函数调用机制,最终要调用子类实现的接口,结果忘记在父类中将对应的接口声明为virtual,导致形成多态的机制,没有调用到子类实现的函数。

        一定要记住,要实现多态下的函数调用,父类的相关接口必须声明为virtual。

class Shape(){    // ...     virtual void Draw();     // ...} 

多态是C++中核心概念之一,代码到处都有多态的身影,一定要吃透多态的概念,理解虚函数调用是如何实现的。关于C++多态中虚函数的调用,可以参见文章:

几秒读懂C++虚函数调用的汇编代码实现https://blog.csdn.net/chenlycly/article/details/121046234

8、该使用双指针的地方,却使用了单指针

       有时,我们需要调用一个接口去获取某些数据,接口中将数据拷贝到传入的参数对应的内存中,此时设计参数时会传入指针或引用。

       比如,我们在调用GetData之前定义了结构体指针p,并new出了对应的结构体对象内存,应该在定义GetData接口时应该使用双指针(指针的指针)的,结果错写成了单指针。有问题的代码如下:

struct CodecInfo     // 编码信息{    int nFrameRate;     // ...};  CodecInfo* pInfo = new CodecInfo;// 出问题的点GetAudioCodecPtr()->GetCodecInfo(pInfo);   // 调用AudioCodec::GetCodecInfo获取编码信息  AudioCodec::GetCodecInfo( CodecInfo* pInfo)  // 此处的参数不应该使用单指针{    memcpy(pInfo, m_codecInfo, sizeof(CodecInfo));}

       上图中的AudioCodec::GetCodecInfo接口的参数不应该为单指针,应该用双指针,修改后的代码应该如下:

AudioCodec::GetCodecInfo( CodecInfo** pInfo)  // 此处的参数类型使用双指针{    memcpy(*pInfo, m_codecInfo, sizeof(CodecInfo));}

9、发布exe程序时,忘记将exe依赖的C运行时库或MFC库带上了

       比如新人用VS-MFC库编写一个测试用的工具软件,结果在发布release版本程序时,没有将程序依赖的C运行时库带上,导致该工具软件在某些电脑中启动报错,提示找不到C运行时库:

因为程序中依赖了动态版本的运行时库和MFC库,在发布程序时要将这些库带上。有些系统中没有这些库,程序启动时就会报找不到库,就会启动失败。没有经验的新手很容易犯这样的错误。

10、应该使用深拷贝,却用了浅拷贝

       本来应该要进行深拷贝的,却使用了浅拷贝(直接赋值),导致另个不同生命周期的C++对象指向了同一块内存,一个对象将内存释放后,另一个对象再用到这块内存,就造成了内存访问违例,产生异常。

       有个很经典的C++笔试题,让我们实现String类的相关函数,其主要目的就是用来考察对深拷贝与浅拷贝的理解的。题目中给出String类的声明:

class String{public:    String();    String(const String & str);    String(const char* str);    String& operator=(String str);    char* c_str() const;    ~String();    int size() const;private:    char* data;};

让写出上述几个函数的内部实现。这些函数的实现代码如下:

// 1、普通构造函数  String::String(const char *str){    if (str == NULL)    {        m_data = new char[1];// 得分点:对空字符串自动申请存放结束标志'\0'的,加分点:对m_data加NULL判断          *m_data = '\0';    }    else    {        int length = strlen(str);        m_data = new char[length + 1];// 若能加 NULL 判断则更好        strcpy(m_data, str);    }}  // 2、String的析构函数  String::~String(void){    delete[] m_data; // 或delete m_data;  }  // 3、拷贝构造函数  String::String(const String &other)// 得分点:输入参数为const型  {             int length = strlen(other.m_data);    m_data = new char[length + 1];// 若能加 NULL 判断则更好      strcpy(m_data, other.m_data);}  // 4、赋值函数  String & String::operator = (const String &other) // 得分点:输入参数为const型  {    if (this == &other)//得分点:检查自赋值          return *this;     if (m_data)        delete[] m_data;//得分点:释放原有的内存资源      int length = strlen(other.m_data);    m_data = new char[length + 1];//加分点:对m_data加NULL判断      strcpy(m_data, other.m_data);    return *this;//得分点:返回本对象的引用    }

点击全文阅读


本文链接:http://zhangshiyu.com/post/45037.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1