我的编程空间,编程开发者的网络收藏夹
学习永远不晚

详解c++良好的编程习惯与编程要点

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

详解c++良好的编程习惯与编程要点

1、以良好的方式编写C++ class

假设现在我们要实现一个复数类complex,在类的实现过程中探索良好的编程习惯。

Header(头文件)中的防卫式声明

complex.h:


# ifndef  __COMPLEX__
# define __COMPLEX__
class complex
{
    
}
# endif

防止头文件的内容被多次包含。

把数据放在private声明下,提供接口访问数据


# ifndef  __COMPLEX__
# define __COMPLEX__
class complex
{
    public:
        double real() const {return re;}
        double imag() const {return im;}
    private:
        doubel re,im;
}
# endif

不会改变类属性(数据成员)的成员函数,全部加上const声明

例如上面的成员函数:


double real () `const` {return re;}
double imag() `const` {return im;}

既然函数不会改变对象,那么就如实说明,编译器能帮你确保函数的const属性,阅读代码的人也明确你的意图。
而且,const对象才可以调用这些函数——const对象不能够调用非const成员函数。

使用构造函数初始值列表


class complex
{
    public:
        complex(double r = 0, double i =0)
            : re(r), im(i)  { }
    private:
        doubel re,im;
}

在初始值列表中,才是初始化。在构造函数体内的,叫做赋值。

参数尽量使用reference to const

为complex 类添加一个+=操作符:


class complex
{
    public:
        complex& operator += (const complex &)
}

使用引用避免类对象构造与析构的开销,使用const确保参数不会被改变。内置类型的值传递与引用传递效率没有多大差别,甚至值传递效率会更高。例如,传递char类型时,值传递只需传递一个字节;引用实际上是指针实现,需要四个字节(32位机)的传递开销。但是为了一致,不妨统一使用引用。

函数返回值也尽量使用引用

以引用方式返回函数局部变量会引发程序未定义行为,离开函数作用域局部变量被销毁,引用该变量没有意义。但是我要说的是,如果可以,函数应该返回引用。当然,要返回的变量要有一定限制:该变量的在进入函数前,已经被分配了内存。以此条件来考量,很容易决定是否要返回引用。而在函数被调用时才创建出来的对象,一定不能返回引用。

说回operator +=,其返回值就是引用,原因在于,执行a+=b时,a已经在内存上存在了。

而operator +,其返回值不能是引用,因为a+b的值,在调用operator +的时候才产生。

下面是operator+=与'operator +' 的实现:


inline complex & complex :: operator += (const complex & r)
{
    this -> re+= r->re;
    this -> im+= r->im;
    return * this;
}
inline complex operator + (const complex & x , const complex & y)
{
    return complex ( real (x)+ real (y), //新创建的对象,不能返回引用
    imag(x)+ imag(y));
}

在operator +=中返回引用还是必要的,这样可以使用连续的操作:


c3 += c2 += c1;

如果重载了操作符,就考虑是否需要多个重载

就我们的复数类来说,+可以有多种使用方式:


complex c1(2,1);
complex c2;
c2 = c1+ c2;
c2 = c1 + 5;
c2 = 7 + c1;

为了应付怎么多种加法,+需要有如下三种重载:


inline complex operator+ (const complex & x ,const complex & y)
{
    return complex (real(x)+real(y),
                    imag(x+imag(y););
}
inline complex operator + (const complex & x, double y)
{
    return complex (real(x)+y,imag(x));

inline complex operator + (double x,const complex &y)
{
    return complex (x+real(y),imag(y));
}

提供给外界使用的接口,放在类声明的最前面

这是某次面试中,面试官大哥告诉我的。想想确实是有道理,类的用户用起来也舒服,一眼就能看见接口。

2、Class with pointer member(s)

记得写Big Three

C++的类可以分为带指针数据成员与不带指针数据成员两类,complex就属于不带指针成员的类。而这里要说的字符串类String,一般的实现会带有一个char *指针。带指针数据成员的类,需要自己实现class三大件:拷贝构造函数、拷贝赋值函数、析构函数。


class String
{
    public:
        String (const char * cstr = 0);
        String (const String & str);
        String & operator = (const String & str);
        ~String();
        char * get_c_str() const {return m_data};
    private:
        char * m_data;
}

如果没有写拷贝构造函数、赋值构造函数、析构函数,编译器默认会给我们写一套。然而带指针的类不能依赖编译器的默认实现——这涉及到资源的释放、深拷贝与浅拷贝的问题。在实现String类的过程中我们来阐述这些问题。

析构函数释放动态分配的内存资源

如果class里有指针,多半是需要进行内存动态分配(例如String),析构函数必须负责在对象生命结束时释放掉动态申请来的内存,否则就造成了内存泄露。局部对象在离开函数作用域时,对象析构函数被自动调用,而使用new动态分配的对象,也需要显式的使用delete来删除对象。而delete实际上会调用对象的析构函数,我们必须在析构函数中完成释放指针m_data所申请的内存。下面是一个构造函数,体现了m_data的动态内存申请:



inline 
String ::String (const char *cstr = 0)
{
    if(cstr)
    {
        m_data = new char[strlen(cstr)+1];   // 这里,m_data申请了内存
        strcpy(m_data,cstr);
    }
    else
    {
        m_data= new char[1];
        *m_data = '\0';
    }
}

这个构造函数以C风格字符串为参数,当执行


String *p = new String ("hello");

m_data向系统申请了一块内存存放字符串hello:

析构函数必须负责把这段动态申请来的内存释放掉:


inline 
String ::~String()
{
    delete[]m_data;
}

赋值构造函数与复制构造函数负责进行深拷贝

来看看如果使用编译器为String默认生成的拷贝构造函数与赋值操作符会发生什么事情。默认的复制构造函数或赋值操作符所做的事情是对类的内存进行按位的拷贝,也称为浅拷贝,它们只是把对象内存上的每一个bit复制到另一个对象上去,在String中就只是复制了指针,而不复制指针所指内容。现在有两个String对象:


String a("Hello");
String b("World");

a、b在内存上如图所示:

如果此时执行


 b = a;

浅拷贝体现为:

存储World\0的内存块没有指针所指向,已经成了一块无法利用内存,从而发生了内存泄露。不止如此,如果此时对象a被删除,使用我们上面所写的析构函数,存储Hello\0的内存块就被释放调用,此时b.m_data成了一个野指针。来看看我们自己实现的构造函数是如何解决这个问题的,它复制的是指针所指的内存内容,这称为深拷贝



inline String &String ::operator= (const String & str)
{
    if(this == &str)           //①
        return *this;
    delete[] m_data;        //②
    m_data = new char[strlen(str.m_data)+1];        //③
    strcpy(m_data,str.m_data);            //④
    return *this
}

这是拷贝赋值函数的经典实现,要点在于:

① 处理自我赋值,如果不存在自我赋值问题,继续下列步骤:

② 释放自身已经申请的内存

③ 申请一块大小与目标字符串一样大的内存

④ 进行字符串的拷贝

对于a = b,②③④过程如下:

同样的,复制构造函数也是一个深拷贝的过程:


inline String ::String(const String & str )
{
    m_data = new char[ strlen (str) +1];
    strcpy(m_data,str.m_data);
}

另外,一定要在operator = 中检查是否self assignment假设这时候确实执行了对象的自我赋值,左右pointers指向同一个内存块,前面的步骤②delete掉该内存块造成下面的结果。当企图对rhs的内存进行访问是,结果是未定义的。

3、static与类

不和对象直接相关的数据,声明为static

想象有一个银行账户的类,每个人都可以开银行账户。存在银行利率这个成员变量,它不应该属于对象,而应该属于银行这个类,由所有的用户来共享。static修饰成员变量时,该成员变量放在程序的全局区中,整个程序运行过程中只有该成员变量的一份副本。而普通的成员变量存在每个对象的内存中,若把银行利率放在每个对象中,是浪费了内存。

static成员函数没有this指针

static成员函数与普通函数一样,都是只有一份函数的副本,存储在进程的代码段上。不一样的是,static成员函数没有this指针,所以它不能够调用普通的成员变量,只能调用static成员变量。普通成员函数的调用需要通过对象来调用,编译器会把对象取地址,作为this指针的实参传递给成员函数:


obj.func() ---> Class :: fun(&obj);

而static成员函数即可以通过对象来调用,也可以通过类名称来调用。

在类的外部定义static成员变量

另一个问题是static成员变量的定义。static成员变量必须在类外部进行定义:


class A
{
    private:
        static int a; //①
}
int A::a = 10;  //②

注意①是声明,②才是定义,定义为变量分配了内存。

static与类的一些小应用

这些可以用来应付一下面试,在实现单例模式的时候,static成员函数与static成员变量得到了使用,下面是一种称为”饿汉式“的单例模式的实现:


class A
{
        public:
            static A& getInstance();
            setup(){...};
        private:
            A();
            A(const A & rhs);
            static A a;
}

这里把class A的构造函数都设置为私有,不允许用户代码创建对象。要获取对象实例需要通过接口getInstance。”饿汉式“缺点在于无论有没有代码需要a,a都被创建出来。下面是改进的单例模式,称为”懒汉式“:


class A
{
    public: 
        static  A& getInstance();
        setup(){....};
    private:
        A();
        A(const A& rsh);
        ...
};
A& A::getInstance()
{
        static A a;
        return a;
}

"懒汉式"只有在真正需要a时,调用getInstance才创建出唯一实例。这可以看成一个具有拖延症的单例模式,不到最后关头不干活。很多设计都体现了这种拖延的思想,比如string的写时复制,真正需要的时候才分配内存给string对象管理的字符串。

以上就是详解c++良好的编程习惯与编程要点的详细内容,更多关于c++良好的编程习惯与编程要点的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

详解c++良好的编程习惯与编程要点

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C++ 编程规范:养成良好编程习惯,打造高品质代码

C++ 编程规范:养成良好编程习惯,打造高品质代码良好的编程规范是编写高质量、可维护代码的关键。本文将介绍 C++ 编程中遵循的最佳实践,帮助您养成良好的编程习惯。命名约定使用小写字母,特殊情况用下划线隔开(如 snake_case)。
C++ 编程规范:养成良好编程习惯,打造高品质代码
2024-05-23

Python代码风格与编程习惯的重要性

这篇文章将为大家详细讲解有关Python代码风格与编程习惯的重要性,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Python代码风格首先我们要以 PEP8 代码规范为标准,但也无需完全遵守。例如:一行不能
2023-06-15

关于Python Socket编程的要点详解

目录前言什么是socket?如何在Python中创建socket对象?Python的套接字库中有多少种可用的套接字方法?服务器套接字可用的方法有哪些?客户端套接字可用的方法有哪些?Python中可用的通用套接字方法有哪些?PythonSoc
2022-06-02

C++ 成员函数详解:对象方法的泛型编程与模板化

c++++ 成员函数是类内部定义的函数,可访问类的数据成员和变量。泛型编程和模板化使代码可重用且独立于数据类型。泛型编程允许编写可用于不同数据类型的代码,而模板化允许创建可用于不同类型成员函数的类。对于需要计算不同形状面积的程序,可使用模板
C++ 成员函数详解:对象方法的泛型编程与模板化
2024-04-29

C++ 成员函数详解:对象方法的底层实现与编译过程

c++++ 中的成员函数是附加在类中的对象方法,用于操作对象中的数据成员。编译过程包括:实例化:为每个成员函数创建函数指针,存储在对象中;调用机制:编译器自动插入类似于 result = ((_this)->*memberfunction(
C++ 成员函数详解:对象方法的底层实现与编译过程
2024-04-29

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录