C++ 编程技巧分享

侯捷 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 形式接收,也就是声明和返回值的类型必须一致,都是指针类型。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux进程】手把手教你如何调整----进程优先级(什么是优先级?为什么要有优先级?)

目录 一、前言 二、优先级的基本概念 &#x1f95d; 什么是优先级&#xff1f; &#x1f34d; 为什么要有优先级&#xff1f; 三、如何查看并修改 --- 进程优先级 &#x1f347; PRI and NI &#x1f525;PRI&#x1f525; &#x1f525;NI&#x1f525; &#x1f3…

亿发开启极速开单新纪元,解锁业务新速度,提升企业竞争力

我们不断追求卓越&#xff0c;致力于通过技术革新&#xff0c;为客户带来更快捷、更智能、更全面的进销存管理体验。立即更新&#xff0c;享受更高效的业务处理流程。

A股3000点失守是出局还是机会?

今天的大A失守300点&#xff0c;那么A股3000点失守是出局还是机会&#xff1f; 1、今天两市低开&#xff0c;盘中一度跌破3000点&#xff0c;最低回踩到了2985点&#xff0c;盘面出现了两个罕见现象&#xff0c;意味着即将探底回升。 2、盘面出现两个罕见现象&#xff1a; 一是…

【嵌入式开发】UART

目录 一、概述 1.1 常见的通信类别/特点 1.2 常见几种通信 二、UART通信协议 2.1 UART通信介绍 2.2 UART通信协议 物理连接示意图&#xff1a; 三、STM32的UART接口 3.1 STM32的UART特点 3.2 STM32的UART框图分析 3.3 UART初始化步骤 3.4 STM32中UART使用 一、概述…

代码随想录第30天|贪心算法

122.买卖股票的最佳时机II 给你一个整数数组 prices &#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&#xff0c;你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买&#xff0c;然后在 同一天 出售。 返回 你能获得…

Java项目:基于SSM框架实现的绿色农产品推广应用网站果蔬商城水果商城蔬菜商城【ssm+B/S架构+源码+数据库+答辩PPT+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的绿色农产品推广应用网站果蔬商城水果商城蔬菜商城 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能…

kettle无法启动问题_PENTAHO_JAVA_HOME

1&#xff0c;遇到spoon.bat启动报错&#xff1a;先增加pause看清错误信息 1.1&#xff0c;错误信息 1.2&#xff0c;因为本地安装jdk1.6无法支持现有版本kettle。只能手动执行kettle调用的java路径&#xff1b;如下 系统--高级系统设置--高级--环境变量 启动成功

【CMake】CMake从入门到实战系列(十七)—— CMake添加环境检查

&#x1f525;博客简介&#xff1a;开了几个专栏&#xff0c;针对 Linux 和 rtos 系统&#xff0c;嵌入式开发和音视频开发&#xff0c;结合多年工作经验&#xff0c;跟大家分享交流嵌入式软硬件技术、音视频技术的干货。   ✍️系列专栏&#xff1a;C/C、Linux、rtos、嵌入式…

【无线传感网】LEACH路由算法

1、LEACH路由算法简介 LEACH协议,全称是“低功耗自适应集簇分层型协议” (Low Energy Adaptive Clustering Hierarchy),是一种无线传感器网络路由协议。基于LEACH协议的算法,称为LEACH算法。 2、LEACH路由算法的基本思想 LEACH路由协议与以往的路由协议的不同之处在于其改变…

C#.net6.0语言+前端Vue,Ant-Design开发的智慧医院手术室麻醉管理平台源码 什么是手术麻醉临床信息管理系统?

C#.net6.0语言前端Vue,Ant-Design开发的智慧医院手术室麻醉管理平台源码 什么是手术麻醉临床信息管理系统&#xff1f; 手术麻醉临床信息管理系统涵盖了手术进程管理、自动排班、手术记录、术前评估与麻醉记录等功能&#xff0c;强调了系统如何通过技术架构和数据集成提高工作…

python代码生成可执行文件

以下面转换图片尺寸的代码resize_images.py为例&#xff1a; 代码功能&#xff1a;原始图片放在img文件夹中&#xff0c;然后运行代码可以转换成指定分辨率&#xff0c;保存在同一目录下的新生成的文件夹中 import os import sys import cv2 from datetime import datetime f…

第4集《大乘起信论》

请大家打开《讲义》第七页。 解释标题有别释跟合释&#xff0c;在合释当中又分两科。第一个明心&#xff0c;先明白我们内在的种性&#xff0c;这个种性就会产生不同的业力、不同的果报。在明白这个道理以后&#xff0c;我们应该怎么去扭转这个种性呢&#xff1f;就讲到修学的…

贝锐蒲公英异地组网方案:实现制药设备远程监控、远程运维

公司业务涉及放射性药品的生产与销售&#xff0c;在全国各地拥有20多个分公司。由于药品的特殊性&#xff0c;在日常生产过程中&#xff0c;需要符合药品监管规范要求&#xff0c;对各个分部的气相、液相设备及打印机等进行监管&#xff0c;了解其运行数据及工作情况。 为满足这…

MobileNet系列论文阅读笔记(MobileNetV1、MobileNetV2和MobileNetV3)

目录 引言MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications摘要Prior Work -- 先前工作MobileNet Architecture— MobileNet结构Depthwise Separable Convolution—深度可分离卷积Network Structure -- 网络结构 总结 MobileNetV2: Invert…

19 Shell编程之条件语句

目录 19.1 条件测试操作 19.1.1 文件测试 19.1.1 整数值比较 19.1.3 字符串比较 19.1.4 逻辑测试 19.2 if条件语句 19.2.1 if语句的结构 19.2.2 if语句应用示例 19.3 case分支语句 19.3.1 case语句的结构 19.3.2 case语句应用示例 19.1 条件测试操作 Shell环境根据命令执行后…

Javase.认识异常

认识异常 【本章目标】1. 异常的概念与体系结构1.1 异常的概念1.2 异常的体系结构1.3 异常的分类 2. 异常的处理2.1 防御式编程2.2 异常的抛出2.3 异常的捕获2.3.2 try-catch捕获并处理2.3.3 finally2.4 异常的处理流程 3. 自定义异常类 【本章目标】 异常概念与体系结构异常的…

日常工作中常用的管理工具

日常工作中常用的管理工具 SWOT分析法&#xff1a; 帮你清晰地把我全局&#xff0c;分析自己在资源方面的优势域劣势&#xff0c;把握环境提供的机会&#xff0c;防范可能存在的风险与威胁&#xff0c;对我们的成功有非常重要的意义 PDCA循环规则&#xff1a; 每一项工作&#…

安卓设备优雅的命令 adb 以及 优秀的控制 scrcpy

一、背景 如果有多台安卓设备&#xff0c;并为这些设备安装软件&#xff0c;一个个使用u盘再加上鼠标操作虽然可以做到&#xff0c;但是大概率比较麻烦。试想下&#xff0c;如果坐在电脑旁边&#xff0c;就能鼠标在电脑上点点就能解决问题&#xff0c;是多么优雅的一件事情。 …

Leetcode Hot100之双指针

1. 移动零 题目描述 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。解题思路 双指针遍历一遍即可解决: 我们定义了两个指针 i 和 j&#xf…

win10手动安装stable-diffusion-webui

目录 1.python下载安装 2.git下载安装 3.stable-diffusion-webui下载 4.安装s-d-webui的依赖包&#xff08;用国内镜像提速&#xff09; 5.git下载的stable-diffusion-webui&#xff0c;依赖包提示已安装&#xff0c;但运行webui-user.bat后&#xff0c;又开始下载 6.修…