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 pointerc2 += 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.mzph.cn/web/31944.shtml

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

相关文章

小i机器人:总负债5.31亿,员工数量在减少,银行借款在增加,净利润已下降-362.68%

小i机器人:总负债5.31亿,员工数量在减少,银行借款在增加,总收入在增长,净利润已下降-362.68% 来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 目录 一、小i机器人公司介绍 二、小i机器人过去20年的发展历程和取得的成就 三、小i机器人的产品和技术架构 四、小i机器人…

[最新教程]Claude Sonnet 3.5注册方法详细步骤分享,新手小白收藏,文末免费送已注册的Claude账号

一.Claude sonnet 3.5大模型面世 6月21日&#xff0c;被称为“OpenAI 最强竞对”的大模型公司 Anthropic 发布了 Claude 3.5 系列模型中的第一个版本——Claude 3.5 Sonnet。 Anthropic 在官方博客中表示&#xff0c;Claude 3.5 Sonnet 提高了智能化的行业标准&#xff0c;在…

基于卷积神经网络的目标检测

卷积神经网络基础知识 1.什么是filter 通常一个6x6的灰度图像&#xff0c;构造一个3*3的矩阵&#xff0c;在卷积神经网络中称之为filter,对&#xff16;x6的图像进行卷积运算。 2.什么是padding 假设输出图像大小为nn与过滤器大小为ff&#xff0c;输出图像大小则为(n−f1)∗(…

【计算机毕业设计】204基于微信小程序疫情期间学生请假与销假系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

同一天里,两位大厂程序员猝死。。。

2024年&#xff0c;真的不是平静的一年。在几天前&#xff0c;IT行业接连发生了两件不幸的事情。 6月17日下午&#xff0c;东南亚电商公司Sh**ee位于北京的研发中心&#xff0c;一位负责研发的女员工突然在工位上晕倒。 同事们赶紧拨打了120&#xff0c;然而还是没能抢救过来&a…

【验证码识别】Yolov8实战某验3空间推理点选验证码,目标检测,语义分割,颜色分类。

【验证码识别】Yolov8实战某验3空间推理点选验证码&#xff0c;目标检测&#xff0c;语义分割&#xff0c;颜色分类。 文章目录 【验证码识别】Yolov8实战某验3空间推理点选验证码&#xff0c;目标检测&#xff0c;语义分割&#xff0c;颜色分类。声明1.空间推理验证码&#xf…

2024年全国青少信息素养大赛python编程复赛集训第四天编程题分享

整理资料不容易,感谢各位大佬给个点赞和分享吧,谢谢 大家如果不想阅读前边的比赛内容介绍,可以直接跳过:拉到底部看集训题目 (一)比赛内容: 【小学组】 1.了解输入与输出的概念,掌握使用基本输入输出和简单运算 为主的标准函数; 2.掌握注释的方法; 3.掌握基本数…

Studying-代码随想录训练营day17| 654.最大二叉树、617合并二叉树、700.二叉搜索树中的搜索、98.验证二叉树搜索树

第十七天&#xff0c;二叉树part05&#xff0c;进一步学习二叉树&#x1f4aa; 654.最大二叉树 文档讲解&#xff1a;代码随想录最大二叉树 视频讲解&#xff1a;手撕最大二叉树 题目&#xff1a; 学习&#xff1a;本题与利用中序和后序序列构造二叉树有相同之处。依据题目要求…

第五篇:构建与维护私有Docker Registry: 企业级实践指南

构建与维护私有Docker Registry: 企业级实践指南 1. 引言&#xff1a;解析私有Docker仓库的必要性 1.1 Docker Registry简介与私有化的好处 Docker Registry是一个用于存储和分发Docker镜像的系统。在Docker生态系统中&#xff0c;Registry扮演着至关重要的角色&#xff0c;为…

骨传导耳机值不值得入手?五款运动好物骨传导耳机推荐!

开放式耳机在如今社会中已经迅速成为大家购买耳机的新趋势&#xff0c;开放式蓝牙耳机作为骨传导耳机&#xff0c;深受喜欢听歌和热爱运动的人群欢迎。当大家谈到佩戴的稳固性时&#xff0c;后挂式骨传导耳机都会收到一致好评。对于热爱运动的人士而言&#xff0c;高品质的骨传…

A800显卡驱动安装(使用deb安装)

重新安装显卡驱动&#xff0c;查阅了资料将过程记录如下&#xff1a; 1.下载deb安装包 打开nvidia官网查找对应的驱动版本&#xff0c;A800所在的选项卡位置如图&#xff1a; 点击查找后下载得到的是nvidia-driver-local-repo-ubuntu2004-550.90.07_1.0-1_amd64.deb安装包 2.…

UDS服务——RequestDownload(0x34)

诊断协议那些事儿 诊断协议那些事儿专栏系列文章,本文介绍RequestDownload(0x34)—— 请求下载,用于给ECU下载数据的,最常见的应用就是在bootloader中,程序下载工具会发起请求下载,以完成ECU程序的升级。通过阅读本文,希望能对你有所帮助。 文章目录 诊断协议那些事儿…

linux如何部署前端项目和安装nginx

要在Linux上部署前端项目并安装Nginx&#xff0c;你可以按照以下步骤操作&#xff1a; 安装Nginx: sudo apt update sudo apt install nginx 启动Nginx服务: sudo systemctl start nginx 确保Nginx服务开机自启: sudo systemctl enable nginx 部署前端项目&#xff0c;假设前…

萨科微slkor宋仕强论道华强北假货之六

萨科微slkor宋仕强论道华强北假货之六&#xff0c;华强北的假货这么多&#xff0c;搞得客户害怕、同行焦虑&#xff0c;话说“在华强北没有被坑过的&#xff0c;就不是华强北人”。我们金航标Kinghelm&#xff08;www.kinghelm.com.cn&#xff09;公司以前有一个贸易部&#xf…

45、基于深度学习的螃蟹性别分类(matlab)

1、基于深度学习的螃蟹性别分类原理及流程 基于深度学习的螃蟹性别分类原理是利用深度学习模型对螃蟹的图像进行训练和识别&#xff0c;从而实现对螃蟹性别的自动分类。整个流程可以分为数据准备、模型构建、模型训练和性别分类四个步骤。 数据准备&#xff1a; 首先需要收集包…

【报错解决】引入@ComponentScan注解注册bean容器后,导致的接口404问题

引入ComponentScan注解注册bean容器后&#xff0c;导致的接口404问题 背景 由于微服务开发中&#xff0c;经常需要在公共模块在引入一些公共模块&#xff0c;供其他服务使用&#xff0c;但是其他服务需要在启动类中配置ComponentScan注解扫描这个公共模块下注册的 bean&#…

在下游市场需求带动下 我国气调包装机市场规模逐渐扩大

在下游市场需求带动下 我国气调包装机市场规模逐渐扩大 气调包装机又称为气调保鲜包装机&#xff0c;是一种具有气体置换功能的保鲜包装设备。气调包装机的工作原理是将原有的包装内空气抽至真空&#xff0c;再充入一定配比的混合气体&#xff0c;从而对被包装的物品进行有效保…

python-爬虫篇-爬取百度贴吧,段友之家的图片和视频

#!/usr/bin/env python # -*- coding: utf-8 -*-""" 爬取百度贴吧&#xff0c;段友之家的图片和视频 author: cuizy time&#xff1a;2018-05-19 """import requests import bs4 import osdef write_file(file_url, file_type):""&quo…

02 Shell编程之条件语句

1、条件测试操作 要使Shell脚本程序具备一定的智能&#xff0c;面临的第一个问题就是如何区分不同的情况以确定执行何种操作。 例如&#xff0c;当磁盘使用率超过95%时&#xff0c;发送告警信息&#xff1b;当备份目录不存在时&#xff0c;能够自动创建&#xff1b; 当源码编…

超大cvs文件导入MySQL

1 XXX.cvs 太大 使用cvs拆分HugeCSVSplitter_jb51工具进行拆分&#xff0c;Line Count 设置为1,000,000 注意&#xff1a;1 拆分后除第一个子cvs文件含有标题外&#xff0c;其他的子文档都不含有标题行&#xff1b; 2 后一个文档的第一行为前一个文档的…