C++ 编程技巧分享

news2024/11/24 7:27:43

侯捷 C++ 学习路径:面向对象的高级编程 -> STL库 -> C++11新特性 -> cmake

1.1. C 与 C++的区别

在C语言中,主要存在两大类内容,数据和处理数据的函数,二者彼此分离,是多对多的关系。不同的函数可以调用同一个数据,这就导致在开发大型项目的时候,二者纠缠在一起,容易出现问题。

因此推出,面向对象的C++语言,用类将函数和数据进行封装,使得数据只能被某些特定的函数进行处理,效果上类似结构体struct,很好的将数据与函数的多对多关系转变为一对一关系,避免了很多程序错误。

虽然C语言中的 struct 确实可以用来创建复杂的数据类型并在一定程度上模拟面向对象编程(OOP)的思想,但与C++中的 class 相比,C语言的 struct 仍然存在一些重要的限制和区别,这也是为什么C++引入了面向对象的编程概念。以下是一些关键点:

总的来说,虽然在C语言中可以通过 struct 和函数指针等技巧模拟一些面向对象的概念,但C++通过 class 提供了更加完善和直接的支持,使得面向对象编程更加自然和高效。因此,引入C++及其面向对象特性能够大幅简化代码的组织和管理,提升代码的可维护性和扩展性。

1.2. 头文件的防御式声明

在写头文件中代码是,为了避免调用头文件的主文件每次运行都重新读取一遍头文件中的内容,降低程序运行效率,需要我们在头文件中进行防御式声明,如下所示,其中COMPLEX是我们自己定义的名称,用于区分不同的头文件:

#ifndef __COMPLEX__
#define __COMPLEX__

...

#endif

如此一来,主程序只会在第一次调用头文件时读取头文件的完整内容,避免了反复读取重复内容的操作

1.3. 头文件的布局

#ifndef __COMPLEX__
#define __COMPLEX__

// 前置声明
#include <cmath>

class ostream;
class complex;

complex&
  __doapl (complex8* this, const complex& r);

// 类的声明
class complex
{
...
};

// 类的定义
complex::function ...

#endif

1.4. inline(内联)函数

简单来讲,定义为 inline 的函数运行效率更高,但即使你将所有的函数都定义为 inline 函数,也不能保证程序的效率会更好,这是因为一个函数最终是否成为 inline 函数,是由编译器决定的,你的 inline 关键字只是建议。函数若在 class body 内完成定义,便自动成为 inline 的“候选人”。

class complex
{
public:
  complex(double r=0,doublei=0)
  : re(r),im(i)
  {}
  complex& operator += (const complex&);
  double real () const { return re; }
  double imag () const { return im; }
private:
  double re,im;

  friend complex& __doapl(complex*, const complex&);
};
inline double 
imag(const complex& x)
{
  return x.image(); 
}

1.5. access level(访问级别)

私域数据要放在 private 中,方法(函数)根据你是否希望他人看到进行区分,分别放在 public 和 private 部分

1.6. 构造函数和析构函数

  • 构造函数不能在类内使用,他是专门为了类的实例化而存在的。
  • 不带指针的类多半不用写析构函数,因为不需要手动释放内存。
  • 构造函数可以有很多个 - overloading(重载),实际场景中经常用到。

初始化参数列表:complex(double r = 0,double i = 0) : re(r), im(i) {},如果不用初始化参数列表对变量进行赋初值,而是在构造函数体内对变量进行赋值,相当于你跳过了初始化参数的过程,虽然大部分情况下对接过没有影响,但程序效率降低。

class complex
{
public:
  complex (double r = 0,double i = 0)
    : re(r),im(i)
  {}
  complex(): re(r), im(i) {}
  complex& operator += (const complex&);
  double real () const { return re; }
  double imag () const { return im; }
private:
  double re, im;

  friend complex& __doapl(complex*, const complex&);
};
void real(double r) { re = r; }

在上面的例子中,我们重载了 real 函数,函数名字相同,但 real 函数编译后的实际名称是不同的,可能如下:

?real@Complex@@QBENXZ
?real@Complex@@AQENABN@Z
// 这个构造函数有两个参数 r 和 i,并且都提供了默认值。这意味着如果不提供参数或提供部分参数,都可以调用这个构造函数。例如:
complex(double r=0,doublei=0) : re(r),im(i) {}
// 这个构造函数没有参数。它是一个典型的默认构造函数,用于在没有提供任何参数的情况下初始化对象。
complex(): re(r), im(i) {}

上面这两种构造函数的方式是冲突的。在C++中,构造函数重载的判别是基于参数的数量和类型。在您的代码中,默认参数的构造函数 complex(double r = 0, double i = 0)本质上已经涵盖了无参数的情况。

因此,这两个构造函数之间存在歧义,因为编译器不能明确区分它们:

comple c1;complex c2();这两种实例化方式都没有传递参数,可以被解释为 complex(double r = 0, double i = 0),也可以被解释为Complex()。导致编译器无法确定在调用构造函数时,应该使用哪个构造函数,因此会导致冲突和编译错误。

1.7. 把构造函数放在 private 区域中

一般情况下,构造函数不会写在 private 区域中,因为这会导致该类无法实例化,但存在特例,即单例模式(Singleton)。

class A {
public:
    // 获取单例实例的静态方法
    static A& getInstance();
    
    // 一些公有方法
    void setup() { ... }

private:
    // 私有的构造函数
    A();
    // 私有的拷贝构造函数
    A(const A& rhs);
    // 其他私有成员...
};

// 获取单例实例的静态方法实现
A& A::getInstance() {
    static A a; // 静态局部变量,确保只会被实例化一次
    return a;
}

// 使用单例实例并调用setup方法
A::getInstance().setup();

  1. 单例模式(Singleton Pattern):单例模式是一种设计模式,它限制一个类只能有一个实例,并提供一个全局访问点。通过单例模式,可以确保一个类只有一个实例,并且该实例易于访问。
  2. 静态方法 getInstance:这是单例模式的关键方法。通过这个静态方法,可以访问唯一的实例。在该方法内部,定义了一个静态局部变量 static A a;。由于局部静态变量只会在第一次调用时被初始化,因此 a 只会被创建一次,确保了单例的特性。
  3. 私有的构造函数:构造函数被定义为私有,意味着外部无法直接创建类的实例。这是实现单例模式的关键之一。只有类自身(通过 getInstance 方法)可以访问和创建其实例。
  4. 私有的拷贝构造函数:拷贝构造函数也被定义为私有,以防止类的实例被复制。单例模式需要确保只有一个实例,因此也需要防止复制行为。

单例模式通过以下方式确保类只有一个实例:

  • 控制实例化:通过将构造函数设为私有,禁止外部代码直接创建实例。
  • 提供全局访问点:通过一个公共的静态方法(如 getInstance)提供对唯一实例的访问。
  • 防止复制:通过将拷贝构造函数和赋值操作符设为私有,防止复制类的实例。

在单例模式之外,构造函数定义在private区域的情况还包括:

  • 工厂模式(Factory Pattern):通过工厂方法创建类的实例,而不是直接通过构造函数。
  • 控制对象的生命周期:例如,确保对象只在特定条件下被创建。

1.8. 常量成员函数

class 类里面的函数可以分成两种:会改变数据的和不会改变数据的。为保险起见,所有不会改变数据内容的类内成员函数都应该声明为常量成员函数,即:double real () const { return re; }

如果类内的成员函数不声明为常量成员函数,会导致函数调用时发生冲突:

const complex c1(2, 1);
cout << c1.real();
cout << c2.image();

上面的代码中,实例化的一个常数类,不能通过调用成员函数修改变量内容,但如果类内成员函数的定义过程中没有加 const,而是写成了double real () { return re; }double imag () { return im; },就会导致编译器“丈二和尚摸不着头脑”,不知道到底能不能修改,导致冲突。

1.9. 参数传递:pass by value vs. pass by reference(to const)

// pass by value(值传递,将至本身进行传递,速度较慢,取决于传递值的大小)
complex(double r = 0,double i = 0) : re(r),im(i) {}
// pass by reference(地址传递,速度很快,就是传递一个指针的速度,即四个字节)
complex& operator += (const complex&);   //带const,意味着在函数中不能修改complex&类型的变量

最好所有的参数传递都传引用,尽量不要传值,如果传过去但又不希望对方改的话,加上个const

1.10. 返回值传递:return by value vs. return by reference(to const)

complex& operator += (const complex&);
double real () const { return re; }
double imag () const { return im; }
friend complex& __doapl(complex*, const complex&);
  • 上面的代码中,第一行和最后一行代码的返回值就是引用。
  • 最好所有的返回值传递都传引用,尽量不要传值,如果传过去但又不希望对方改的话,加上个const

1.11. friend(友元)

一般情况下,数据定义在 private 域中(数据的封装),外部想要拿到需要通过类内 public 域中的函数拿到,但也存在一种特殊情况,我们希望某些“朋友”函数可以直接拿到 private 域中的数据,这时就可以定义友元:

class complex
{
public:
  complex(double r=0,doublei=0)
  : re(r),im(i)
  {}
  complex(): re(r), im(i) {}
  complex& operator += (const complex&);
  double real () const { return re; }
  double imag () const { return im; }
private:
  double re, im;

  friend complex& __doapl(complex*, const complex&);
};

inline complex&
__doapl (complex* ths, const complex& r)
{
  ths->re += r.re;
  ths->im += r.im;
  return *ths;
}

通过友元拿数据要比通过类内函数拿数据更快速,但尽量不要建立太多友元,因为友元实际上是对类的封装的破坏

1.12. 相同类(class)的各个对象(object)互为友元(friend)

class complex
{
public :
  complex(doubler=0,doublei=0) 
  : re(r), im(i) 
  { }
  
  int func(const complex& param)
  {return param.re + param.im;}
  
private:
  double re,im;
};
{
  complex c1(2, 1);
  complex c2;

  c2.func(c1);
}

1.13. C++编程规范总结

  1. 所有的数据都要放在 private 域当中
  2. 参数尽可能通过引用(reference)进行传递,看情况考虑要不要加 const
  3. 返回值尽量通过引用(reference)进行传递,但存在不能通过引用进行传递的情况
  4. 在类的 body 内的函数,应加 const 的函数(不改变传入参数和数据的函数)都应该加上
  5. 构造函数在传参时,尽量使用参数化列表方式进行传递

1.14. class body 外的各种定义

问题:

什么情况下可以 pass by reference

  • 只要传入的参数的值不发生改变,就可以 pass by reference
  • 修改调用者的变量:如果函数需要修改传入的变量,则使用引用传递。通过引用传递,函数可以直接操作原始变量,而不是其副本。
  • 避免复制开销:对于大对象或复杂对象,传递引用可以避免对象的复制开销,提高效率。
  • 传递数组:在C++中数组不能直接按值传递,因此通常使用引用或指针来传递数组。

什么情况下可以 return by reference

  • 返回的引用必须引用一个有效的对象,而不是局部变量。局部变量在函数返回时会被销毁,返回它们的引用会导致悬空引用。
  • 返回类成员:如果函数返回类的某个成员,可以使用引用返回以允许对该成员进行修改。
  • 允许链式操作:引用返回可以使得调用者能够连续调用函数,例如常见的链式调用。
  • 返回容器元素:当返回容器中的元素时,使用引用返回可以避免复制元素并允许修改元素。

1.15. 操作符重载(operator overloading)

1.15.1. 成员函数

inline complex&
__doapl(complex* ths, const complex& r)
{
  ths->re += r.re;
  ths->im += r.im;
  return *ths
}

inline complex&
complex::operator += (const complex& r)
{
  return __dopal (this, r)
}

{
  complex c1(2, 1);
  complex c2(5);

  //c1不发生改变,pass by reference;c2发生改变,pass by pointer
  c2 += c1; 
}

1.15.2. 非成员函数

上面这三个函数不能 return by reference,因为这三个函数的返回值必定是 local object(函数返回后值被清空)

typename();这种语法被用来创建临时对象,好处是不用给变量其名字,具体用法如:return complex (real(x) + y, imag(y));

临时对象这种用法平时很少用到,但在标准库中经常用到。

1.16. return by reference 语法分析

先来看一段代码:

inline complex&
__doapl(complex* ths, const complex& r)
{
  ths->re += r.re;
  ths->im += r.im;
  return *ths
}

在这段代码中,我们生命的函数返回值类型是complex&,是一种引用类型,但函数实际上返回的是*ths,也就是ths指针中的内容,这似乎出现了矛盾,但实际上这种写法并没有问题,这是因为:传递者无需知道接受者是以 reference 形式接收的;如果通过 pointer 的形式进行传递,传递者必须知道接受者是以 pointer 形式接收,也就是声明和返回值的类型必须一致,都是指针类型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1849911.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker开机自动重启及自动启动容器

Docker开机自动重启及自动启动容器 Windows开机自动重启设置容器自动启动 Windows开机自动重启 勾选 Start Docker Desktop when you sign in to your computer 设置容器自动启动 1.docker update 命令 Usage: docker update [OPTIONS] CONTAINER [CONTAINER...]Update co…

32.基于分隔符解决黏包和半包

LineBasedFrameDecoder 基于换行/n (linux)或回车换行/r/n(windows)进行分割。 使用LIneBasedFrameDecoder构造方法,需要设定一个最大长度。 如果超过了最大长度,还是没有找到换行符,就这位这个数据段太长了,抛出ToolLongFrameException DelimiterBasedFrameDecoder …

IF膨胀时代,“水刊”当赢?2023热门“水刊”影响因子详解!

【欧亚科睿学术】 1 “四大水刊”详情 图片来源&#xff1a;欧亚科睿学术整理 “四大水刊”的影响因子均有所下跌&#xff0c;其中&#xff0c;曾经被列入中科院预警名单的期刊MEDICINE&#xff0c;其影响因子已是连续三年持续下降。从JCR分区来看&#xff0c;四本期刊分区均…

新手(初学者)学R语言第一课,从学正确导入数据开始

初看题目好像我在教你怎么导入数据&#xff0c;不不不&#xff0c;我是在教你正确的导入数据&#xff0c;不是说数据导入R就叫正确导入数据了。本章为新手教程&#xff0c;老手可以跳过。 这个内容早就想写了&#xff0c;今天有点空和大家聊一下。为什么R语言对于新手而言不太友…

threeJS 基础 03---动画

1.动画效果渲染循环 注&#xff1a; 使用循环渲染时&#xff0c;不用手动渲染到画布且再次调用监听更新事件&#xff0c;两者只用使用其中之一即可 周期循环&#xff0c;默认理想状态下每秒循环60次 requestAnimationFrame 渲染帧率&#xff08;详情见threeJs的文档&#xff…

区块链会议投稿资讯CCF A--WINE 2024 截止7.15 附录用率 附录用的区块链文章

Conference&#xff1a;The Conference on Web and Internet Economics (WINE) CCF level&#xff1a;CCF A Categories&#xff1a;Cross-cutting/comprehensive/emerging Year&#xff1a;2024 Conference time&#xff1a; December 2-5, 2024 录用率&#xff1a; sele…

Pyqt5 + Qt Creator实现QML开发环境配置

先安装Qt Creator, 该软件主要是为了编辑QML文件 在pycharm中配置外部插件&#xff0c;实现Qt Creator的调用 配置完成后&#xff0c;右击qml文件选择Qt Creator就可以直接进行编辑了

高效22KW双向DCDC储能、充电电源模块项目设计开发

22kW 双向CLL谐振变换器的目标是输出电压范围宽、高效率和高功率密度的双向应用&#xff0c;如电动汽车车载充电器和储能系统。研究了一种新的灵活的 CLLC 双向谐振变换器增益控制方案&#xff0c;以便在充放电模式下实现高效率和宽电压增益范围。得益于 Wolfspeed C3MTM 1200V…

Python+Selenium自动化入门

本章内容需有一定Python基础&#xff0c;如何不懂的&#xff0c;请先学习Python。 什么&#xff1f;&#xff1f;没有好的学习资料&#xff0c;给你准备好了&#xff01;&#xff01; Web自动化环境搭建 1、软件准备 python64位安装包chrome64位浏览器&驱动浏览器驱动下…

基于SpringBoot+协同过滤算法的家政服务平台设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

Java 超详细实现导入导出 (包含时间转换问题和样式)

序言 工作中遇到了导入导出问题&#xff0c;并且出现了导入或导出Excel时间格式变为数字的问题。通过学习解决实现了这些功能&#xff0c;记录总结分享给大家。本文将详细介绍如何使用 Java 编程语言和 Apache POI 库来实现这些功能。我们将通过一个示例项目演示如何从数据库中…

STM32学习笔记(十)--I2C、IIC总线协议详解

概述&#xff1a;Inter Integrated Circuit&#xff0c;一组多从 多组多从 有应答 是一种同步&#xff08;具有时钟线需要同步时钟SCL&#xff09;、串行&#xff08;一位一位的往一个方向发送&#xff09;、半双工&#xff08;发送接收存在一种&#xff09;通信总线。 &…

代码随想录算法训练营第六十七天 | 字符串接龙、有向图的完全可达性、岛屿的周长

字符串接龙 文字讲解&#xff1a;110. 字符串接龙 | 代码随想录 解题思路 本题只需要求出最短路径的长度就可以了&#xff08;想到广搜&#xff09;&#xff0c;不用找出具体路径。 所以这道题要解决两个问题&#xff1a; 图中的线是如何连在一起的起点和终点的最短路径长…

Stable Diffusion AI绘画助力建筑设计艺术创新——城市建筑设计大模型分享

大家好&#xff0c;我是向阳 今天我将针对建筑设计方面的AI大模型进行简单介绍&#xff0c;我们将通过富有想象力的关键词或结合Stable Diffusion 的ControlNet 给原本只有黑白线条的线稿变成彩色的效果图&#xff0c;可能你只需要短短几分钟就可以让黑白线稿变成几种甚至十几种…

临时关闭Windows安全中心

在使用WindowsOS是&#xff0c;微软安全中心是我们必不可少的安全防护&#xff0c;但有时我们也会产生想要将其关闭的需求&#xff0c;下面将要介绍如何临时关闭Windows的安全中心 一、打开安全中心、选择“病毒与威胁防护”&#xff0c;点击“管理设置” 之后将其实时保护关闭…

材料科学SCI期刊,中科院3区,收稿范围广,易录用

一、期刊名称 International Journal of Material Forming 二、期刊简介概况 期刊类型&#xff1a;SCI 学科领域&#xff1a;材料科学 影响因子&#xff1a;2.4 中科院分区&#xff1a;3区 三、期刊征稿范围 该杂志发表和传播材料成型领域的原创研究。该研究应构成对材料…

multiprocessing多进程计算及与rabbitmq消息通讯实践

1. 需求与设计 我所设计的计算服务旨在满足多个客户对复杂计算任务的需求。由于这些计算任务通常耗时较长且资源消耗较大&#xff0c;为了优化客户体验并减少等待时间&#xff0c;我采取了并行计算的策略来显著提升计算效率。 为实现这一目标&#xff0c;我计划利用Python的m…

展讯-源码编译

1.硬件要求 编译主机要求&#xff0c;注意尽量不要使用虚拟机 CPU&#xff1a;Intel(R) Core(TM) i7-4790 CPU 3.60 GHz 内存&#xff1a;8G 硬盘&#xff1a;500G SSD Ubuntu 版本&#xff1a;Ubuntu 64-bit 14.04.5 LTS 这是最低版本要求&#xff0c;实际性能越高越好 2…

尽管担任社长 宫崎英高95%时间还是在做游戏

宫崎英高表示&#xff0c;虽然他是 FromSoftware 工作室的社长&#xff0c;但他日常工作的绝大部分时间都是在指导游戏开发。因魂系列大获成功而成为 FromSoftware 社长的宫崎英高在接受《卫报》采访时表示&#xff0c;企业高管生活并不是他的兴趣所在。 “我喜欢帮助年轻的监督…

数据库精选题(一)(关系数据库设计)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;数据库 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 前言 练习题 题型一&#xff1a;判断关系…