【编程陷阱】编写出色C++代码:遵循的注意事项和最佳实践

简介: 【编程陷阱】编写出色C++代码:遵循的注意事项和最佳实践



🕵️‍♀️1. 强制类型转换注意点

C++的格式转化和C语言不同,

  1. 静态强制类型转换 (static_cast)
  2. 动态强制类型转换 (dynamic_cast)
  3. 常量强制类型转换 (const_cast)
  4. 旧式的 C 风格转换函数

程序示例:

#include <iostream>
using namespace std;
class Base {
public:
    virtual void foo() {}
};
class Derived : public Base {
    // ...
};
int main() {
    // 静态强制类型转换
    double myDouble = 3.14;
    int myInt = static_cast<int>(myDouble);
    cout << "Static Cast: " << myInt << ", Type: " << typeid(myInt).name() << endl;
    // C语言旧式强制类型转换
    double myOldDouble = 4.56;
    int myOldInt = (int)myOldDouble;
    cout << "Old-Style Cast: " << myOldInt << ", Type: " << typeid(myOldInt).name() << endl;
    // 动态强制类型转换
    Base* basePtr = new Derived;
    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
    if (derivedPtr) {
        cout << "Dynamic Cast: Successful, Type: " << typeid(*derivedPtr).name() << endl;
    } else {
        cout << "Dynamic Cast: Failed" << endl;
    }
    // 常量强制类型转换,使用 const_cast 移除 const 修饰符
    const int myConstInt = 42;
    int* myMutableInt = const_cast<int*>(&myConstInt);
    *myMutableInt = 99;
    cout << "Const Cast: " << *myMutableInt << ", Type: " << typeid(*myMutableInt).name() << endl;
    return 0;
}

🕵️‍♀️2. 处理字符串注意点

🌐2.1 按行获取字符串 | 字符数组 + 指针问题

🔍2.1.1 C++解决方案

按行获取字符串

  1. 对于字符数组:cin.getline(ch,MaxSize);
  2. 对于字符串:getline(cin,str);

两者使用scanf都不加&

/***字符数组***/
//输入方式一
char ch[100] = { 0 };
cin.getline(ch, sizeof(ch));
//输入方式二
char ch2[100] = { 0 };
scanf("%s", ch2);//不加&,而%c需要加&
/***字符串***/
//输入方式一
string s1;
getline(cin,s1);
//输入方式二
string s2[100] = { 0 };
scanf("%s", s2);//不加&,而字符串指针需要加&

指针

  1. 字符指针:不加&
  2. 字符串指针:加&
//字符指针
char str[100]="hello";
char *ptr=str;//不是char *ptr= &str;   并且*ptr指向第一个字符元素h
//字符串指针
string s1="hello";
string *p1=&s1;//不是string *p1=s1;  *p代表整个12字符串
char *p2=&s1[0];
cout<<*p1<<endl;

字符串最后一个字符为'\0'

程序示例:

#include <iostream>
using namespace std;
int main() {
    /*字符数组*/
    //输入方式一
    char ch[100] = { 0 };
    cin.getline(ch, sizeof(ch));
    //字符指针
    char *ptr=ch;//不是char *ptr= &ch;   并且*ptr指向第一个字符元素
    for(;*ptr!='\0';ptr++){
        cout<<*ptr;
    }
    cout<<endl;
    //输入方式二
    char ch2[100] = { 0 };
    scanf("%s", ch2);//不加&,而%c需要加&
    /*字符串*/
    //输入方式一
    string s1;
    getline(cin,s1);
    //字符串指针
    string *p1=&s1;//不是string *p1=s1;  *p代表整个12字符串
    cout<<*p1<<endl; 
    //输入方式二
    string s2[100] = { 0 };
    scanf("%s", s2);//不加&,而字符串指针需要加&
    return 0;
}
🔍2.1.2 C语言解决方案

按行获取字符串:char *fgets(char *str, int n, FILE *stream);

  • str 是一个指向字符数组的指针,用于存储读取的字符。
  • n 是要读取的最大字符数(包括终止符 \0),即字符数组的大小。
  • stream 是文件流指针,指定从哪个文件流中读取字符,通常可以是 stdin(标准输入)、stdout(标准输出)等。
  • 注意str会包含换行符,需要删去换行符:   str[strcspn(buffer, "\n")] = '\0';
const int MaxSize=100;
char buffer[MaxSize];
fgets(buffer, sizeof(buffer), stdin);//按行获取字符串
buffer[strcspn(buffer, "\n")] = '\0';//不能是'\n'

程序示例:

#include <stdio.h>
#include <string.h>
int main() {
    const int maxSize = 100;
    char buffer[maxSize];
    printf("Enter a line of text:\n");
    fgets(buffer, sizeof(buffer), stdin);//按行获取字符串
    buffer[strcspn(buffer,"\n")]='\0';//不能是'\n'
    printf("You entered: %s", buffer);
    return 0;
}

🌐2.2 获取字符串长度

最好使用

int slen=s.length();

在使用slen.直接使用s.length()造成的问题会很麻烦,

问题详见【数据结构】模式匹配之KMP算法与Bug日志

#include <iostream>
#include <string>
using namespace std;
int main(){
    string s;
    getline(cin,s);//hello
    cout<<s.length()<<endl;//5
    //而使用s1.length报错
    return 0;
}

🌐2.3 字符串和字符数组的结束标志

字符串结束标志是'\0',若字符数组是以字符串形式输入,则结束标志也是'\0'

注意:

  1. '\0'是结束标志,而不是"\0"
  2. '\n'是换行符而不是"\n"

🕵️‍♀️3. C/C++的结构体注意点

在C语言,结构体声明和C++不同,见😎3.2 KiKi设计类继承

解决办法:C++中设计结构体不要用typedef

/*C语言*/
typedef struct Shape{
    int x,y;
}Shape;
//后续定义才能为   Shape shape;
struct Shape{
    int x,y;
};
//后续定义为   struct Shape shape;
/*C++*/
typedef struct Shape{
    int x,y;
}Shape;
//这样做导致声明了   Shape为全局变量,可能导致后续Shape类型出现问题
//,所以在C++尽量不要使用typedef  
//上述内容等价于
struct Shape {
    int x, y;
};
typedef struct Shape Shape;
struct Shape{
    int x,y;
};
//后续定义为   struct Shape shape;   或者   Shape shape;

🕵️‍♀️4. 继承注意点

🌐4.1 构造函数

派生类的构造函数,需要保证调用基类的构造【默认调用基类无参构造,如果基类创新提供了新的有参构造,则派生类的构造易出错】,见😎3.3 牛牛的书

解决办法:最好每次提供新的构造函数时都再提供一个无参的默认构造函数。

🔍4.1.1 构造函数的形参

构造函数的参数最好不要和class 的数据同名,否则需要加上this,不然出错!

#include <iostream>
using namespace std;
class Base {
    private:
    int x;
    int y;
    public:
    //构造函数
    Base(int x, int y) {
        this->x = x;//x=x is error
        this->y = y;//y=y is error
    }
    Base(){}
    int getX() {
        return x;
    }
    int getY() {
        return y;
    }
};
int main() {
    int x, y;
    cin >> x;
    cin >> y;
    Base base(x,y);
    cout<<base.getX()<<" "<<base.getY()<<endl;
    return 0;
}
🔍4.1.2 构造函数的继承

派生类的构造函数调用前需要调用基类的构造函数,并且派生类新增数据需要加this,否则出错。

#include <iostream>
using namespace std;
class Base {
    private:
        int x;
        int y;
    public:
        //构造函数
        Base(int x, int y) {
            this->x = x;
            this->y = y;
        }
        Base(){}
        int getX() {
            return x;
        }
        int getY() {
            return y;
        }
};
class Sub : public Base {
    private:
        int z;
    public:
        //构造函数
        Sub(int a, int b, int c) :Base(a, b) {//继承基类的构造
            this->z=z;//必须加this,虽然基类没有z成员
        }
        Sub():Base(){}
        int getZ() {
            return z;
        }
        int calculate() {
            return Base::getX()*Base::getY()*this->z;//调用基类数据成员需要加域  Base::
        }
};
int main() {
    int x, y, z;
    cin >> x;
    cin >> y;
    cin >> z;
    Sub sub(x, y, z);
    cout << sub.calculate() << endl;
    return 0;
}

🌐4.2 纯虚函数

纯虚函数:

纯虚函数本身在基类中没有具体的实现,而是在派生类中被强制要求实现。

纯虚函数的声明和定义的一般形式如下:

class AbstractBase {
public:
    virtual void A() const = 0;  // 纯虚函数声明,不是xx(){} const=0;
    virtual ~AbstractBase() {}  // 虚析构函数
};

注意:

  1. 后续派生类实现必须是
class AbstractBase {
public:
    virtual void A() const = 0;  // 纯虚函数声明,不是xx(){} const=0;
    virtual ~AbstractBase() {}  // 虚析构函数
};
class B{
    public:
    virtual void A() const{//virtual可以省略,const不能省略
    ...
    }
    ...
}

🕵️‍♀️5. 限制输出注意点

C++使用cout<<进行输出。

添加头文件#include   +  #include

注意

  1. 输出%,则printf("%%");
  2. setprecision、fixed会影响接下来的所有浮点数的输出,直到下个setprecision、fixed出现为止。
  3. setprecision(2)表示精确2位,如 11.235 则输出 11   3.14 输出 3.1
  4. 要求精确到小数点后n位,使用cout<
/*限定输出格式*/
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
    double a = 11.2345;
    cout << setprecision(2) << a << endl; // 精确输出2个,这里输出11
    cout << fixed<<setprecision(2) << a << endl; // 保留2位小数,输出11.23
    cout << setw(8) << a << endl;          // 控制输出宽度为8,右对齐(默认)
    cout << right << setw(8) << a << endl; // 控制输出宽度为8,右对齐
    cout << left << setw(8) << a << endl; // 控制输出宽度为8,左对齐
}
/*输出
11
11.23
   11.23
   11.23
11.23
*/

🕵️‍♀️6. new/delete注意点

详见:new/delete【C++笔记】

注意:使用new之后求数组大小

int maxSize;
cin>>maxSize;
int *a=new int[maxSize];
//求动态数组大小
int n=sizeof(a)/sizeof(a[0]);//error!  n===0  sizeof(a)===8
int b[5];
int n=sizeof(b)/sizeof(b[0]);//ok

new分配的大小恒等于8

#include <iostream>
using namespace std;
//Array类
class Array{
  private:
  int n;
  int *array;
  public:
  Array(int *a){
        n=sizeof(a)/sizeof(a[0]);
        cout<<"n="<<n<<endl;
    array=new int[n];
    for(int i=0;i<n;i++){
      array[i]=a[i];
    }
  }
  Array(){}
  ~Array(){
    delete [] array;
  }
};
int main(){
  int maxSize;
  cin>>maxSize;
  int *a=new int[maxSize];
  for(int i=0;i<maxSize;i++){
    cin>>a[i];
  }
    int n=sizeof(a)/sizeof(a[0]);
    cout<<"n="<<n<<endl;
  Array array(a);
  return 0;
}


🕵️‍♀️7. 数组注意点

🌐7.1 数组输入

下面的示例,两种输入都正确。

#include <iostream>
using namespace std;
int main(){
    int n;
    cin>>n;
    int *a=new int[n];
    for(int i=0;i<n;i++){
        cin>>a[i];
    }
    for(int i=0;i<n;i++){
        cout<<a[i]<<" ";
    }
    delete [] a;
    cout<<endl;
}


🌐7.2 数组初始化

详见下面的示例

#include <iostream>
using namespace std;
//输出数组
void Disp(int *a,int n){
    for(int i=0;i<n;i++){
        cout<<a[i]<<" ";
    }
    cout<<endl;
}
int main(){
    int a[10]={1,0,2,3,6,5,4,7,8,9};
    int n=sizeof(a)/sizeof(a[0]);
    Disp(a,n);
    a={0,1,2,3,4,5,6,7,8,9};//报错,只能在声明+定义时这样初始化
    Disp(a,n);
    return 0;
}

🕵️‍♀️8. STL

🌐8.1 vector

vector的输入需要注意:详见2.3【C++】STL的基本用法

🕵️‍♀️9. 函数传参注意点

🌐9.1 参数

关于

  1. void sort(vector &v);   //main()传递参数直接  sort(v);
  2. void sort(LNode **L);         //main()传递参数直接  sort(&L);

目录
相关文章
|
1月前
|
存储 C++ UED
【实战指南】4步实现C++插件化编程,轻松实现功能定制与扩展
本文介绍了如何通过四步实现C++插件化编程,实现功能定制与扩展。主要内容包括引言、概述、需求分析、设计方案、详细设计、验证和总结。通过动态加载功能模块,实现软件的高度灵活性和可扩展性,支持快速定制和市场变化响应。具体步骤涉及配置文件构建、模块编译、动态库入口实现和主程序加载。验证部分展示了模块加载成功的日志和配置信息。总结中强调了插件化编程的优势及其在多个方面的应用。
253 64
|
11天前
|
算法 安全 C++
提高C/C++代码的可读性
提高C/C++代码的可读性
30 4
|
1月前
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
122 6
|
1月前
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
51 2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
|
1月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
81 11
|
1月前
|
存储 C++ 容器
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器1
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
54 5
|
1月前
|
Linux C语言 C++
vsCode远程执行c和c++代码并操控linux服务器完整教程
这篇文章提供了一个完整的教程,介绍如何在Visual Studio Code中配置和使用插件来远程执行C和C++代码,并操控Linux服务器,包括安装VSCode、安装插件、配置插件、配置编译工具、升级glibc和编写代码进行调试的步骤。
248 0
vsCode远程执行c和c++代码并操控linux服务器完整教程
|
1月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
44 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
1月前
|
算法 编译器 C++
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
【C++篇】领略模板编程的进阶之美:参数巧思与编译的智慧
80 2
|
1月前
|
存储 编译器 C++
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
39 2