C++全面精通类与对象

目录
  • 运算符重载
  • 运算符复用
  • 前置后置运算符
  • const
  • C++ 的IO流
  • 初始化列表
  • explicit 关键字

运算符重载

C++语法设计很巧妙,比如运算符重载一个 >

bool operator>(const Date& d)
{
     return !(*this <= d);
}

这里可以结合前面的内联函数来进一步提高代码的效率,而内联函数不支持 .h 和 .cpp 分开写,所以成员函数要成为内联函数最好的办法就是把定义放在类里面,类里面定义的会被默认为是 inline 内联函数。

我们计算日期类的加法时:

	Date Date::operator+(int d)
	{
		Date ret(*this);
		ret.day += d;
		while (ret.day > Getmonth(ret.year, ret.month))
		{
			ret.day -= Getmonth(ret.year,ret.month);
			ret.month++;
			if (ret.month == 13)
			{
				ret.year += 1;
				ret.month = 1;
			}
		}
		return ret;
	}

运算符复用

我们可能会有这样的问题,这里面 += 和 + 两个运算符其实是一样的,实现原理上没什么差别,那你可能会封装个函数来解决他们的关系,但是其实直接让他俩互相附庸,分为了 += 复用+ 和 + 复用 += 两种办法:

1.+= 复用 +:

2.+ 复用 +=(更优):

两种乍一看其实没什么区别,但是其实有优越和劣势可以分的,很 += 是不需要构造的,因为它是传引用调用(返回值为域外的 this 指针的内容,必须要传引用),但是 + 是必须要构造的,拷贝局部对象的 ret 和 最后的 return , 一共需要构造两次。

让 += 复用 +,+在先就会让整个过程构造 4 次,而让 + 来复用 += 的话,+ 还是构造 2 次没得说,但是 += 就不需要拷贝构造了,整个过程就只需要构造 2 次,消耗就会小很多。咱就应该多抠抠细节,写出正确的代码固然重要,但是追求更优秀更高效的代码是每一个程序员的基本素养。

前置后置运算符

既然 +,- 能造,那 ++ 和 – 自然也不在话下,但是这就不好玩了啊, num++ 和 ++num 功能上都是 +1,写成运算符重载格式都是

Date Date::operator++();

我们该怎么区分呢?要知道函数名相同而参数不同就应该敏感使用函数重载,C++这个大聪明是不会考虑不到这些的,因此就有了对应的语法:前置不带参数而后置带参数

Date operator++();//前置++

Date operator++(int d);//后置++

Date& operator++()//前置
{
   *this += 1;
   return *this;
}
Date operator++(int)//后置
{
    Date tmp(*this);
    *this += 1;
    return tem;
}

其实括号里面这个参数并没有任何意义,单纯只是用来区分前置与后置的写法,所以这里不写形参也是可以的,我这里就只给了一个类型。还有这里千万不要想着去加一个缺省值,显式传参还好,要是不传参编译器就没办法区分开来,属于是没事找事了。

const

给一个场景:

void Func(const Date& d)

{

d1.Print();

}

void test()

{

Date d1(2022,5,19);

d1.Print();

Func(d1);

}

这个场景下就会报错:

说实在的,这个报错我自己也看的云里雾里,为什么 Print 那里不报错到了 Func 里面 Print 就要报错?Print 传的过去 Func 就传不过去了?究其为什么会报错,其实涉及到一个权限问题。

void Print(Date* const this)
{
     cout<<year<<"-"<<month<<"-"<<day<<endl;
}

我们知道 Print() 的参数其实是 Date& const this ,在上面场景中去调用 Print 时其实参数是 &d1,传对象的地址。在 Print 定义时 const 修饰的是 this 指针,const 修饰的变量可以初始化,此时指针不能被改变但是他指向的内容可以被初始化和修改;而 Func 的 const 修饰 Date*,他指向的内容不能被修改,所以这是一个经典的权限放大问题。

const Date* 要传给 Date* ,所以我们需要一个 const 进行修饰保护,但是 this 本质是一个隐含形参,我们没办法显式调用,也就是说 const 没办法进行修饰。那么C++也提供了一种修饰方法打破这个僵局,就是在函数尾巴加上 const。

void Print() const
{}

尾巴上的 const 编译器就会默认你是加在了函数原本定义的前面,这样就完美了。

C++ 的IO流

我们在代码中使用的 << , >> 为流输入和流提取操作符,只要涉及输入或者输出数据,我们立马想到的就是 cin 和 cout,这俩货其实是全局的对象, cin 对应 istream 类,cout 对应 ostream 类,它们都声明在 头文件中,这也解释了“为什么在 C++ 程序中引入 就可以使用 cin 和 cout”。

我们之所以可以在 <<, >> 之后接上任何类型,是因为强大的语法对每种类型进行了重载,能自动识别类型的本质就是函数重载,所以如果一个 int 类型的流插入 cin<<1 其实是 cin . operator <<(1)。

初始化列表

与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段,初始化列表可以看成是对象的成员变量定义的地方:

class Func
{
public:
    Func(int a):
    _a(a){} // 初始化列表
private:
    int _a;
};

注意:每个成员变量在初始化列表中只能出现一次因为初始化只能初始化一次,还要明确哪些成员必须放在初始化列表进行初始化:

  1. 引用成员变量
  2. const 成员变量
  3. 自义定类型成员(该类没有默认构造函数)

其他变量即可以在初始化列表初始化也可以在函数体内初始化,内置类型成员不处理时,会调用默认构造函数即随机值,如果我给出缺省值,那么之这个值就是给初始化列表用的,如果在初始化列表也同时给出这个内置类型的初始化值,就会采用初始化列表的值。我们应该尽量在初始化列表就初始化完,这样能尽可能的减少很多毛病效率也高。

再来看看这个题目:

这个程序的结果是啥?

答案是 1 和随机值,因为成员变量在类中的声明次序就是他在初始化列表中的初始化顺序,与他在初始化列表中的先后次序无关。_a2 先声明 _a2 = _a1,此时 _a1 为随机值,所以 _a2 为随机值,_a1 为 1。

explicit 关键字

构造函数不仅可以构造和初始化对象,对于单个参数的构造函数,还具有类型转换的作用。在C语言里面我们就知道有隐式类型转换,其实在 C++ 里面也是一样的,比如针对我定义的一个 Date(int year):

Date d1(2022);
Date d2 = 2022;

显然, 这里 d2 需要的是 Date 类型的参数, 而我们传入的是一个int, 这个程序却能成功运行, 就是因为这隐式调用,另外说一句, 在对象刚刚定义时, 即使使用的是赋值操作符 = , 也是会调用构造函数, 而不是重载的 operator= 运算符。这两个语句对应前者是构造,而后者是构造+拷贝构造,相当于发生了隐式类型转换, 如果我们写成:

explicit Date(int year)

这个关键字会阻止这种转换的发生。

到此这篇关于C++全面精通类与对象的文章就介绍到这了,更多相关C++类与对象内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++类与对象的详细说明

    目录 类的引入 类的定义 1.声明和定义全部放在类体中 2.声明放在头文件,定义放在源文件中 类的访问限定符号及封装 访问限定符 封装 类的实例化 类对象模型 this指针 this指针的特性 总结 类的引入 在引入类之前,先来回忆一下C语言中的结构体.结构体是一种自定义类型,可以在其中定义变量,如我们所熟悉的日期结构体: struct Date { int year; int month; int day; }; 而在C++中,结构体被升级成了类,结构体中不仅可以定义成员变量,也可以定义成员函

  • C++浅析类与对象的基础

    目录 面向过程和面向对象 类的引入 访问限定符 封装 类的作用域 类的实例化 面向过程和面向对象 类和对象是 C++ 的核心特性 我们之前的C语言就属于面向过程,关注过程,分析求解问题的步骤再通过函数调用解决问题:而现在C++是基于面向对象,关注对象,将一个问题拆分成不同对象,依靠对象之间的交互完成. 比如有一个图书馆系统,用C语言面向过程思路就是:统计图书,图书分类,同步上架图书数据,记录借阅信息.而面向对象我们会创建两个类,图书馆和用户,我们关心的是图书馆和用户之间的关系,再分别实现交互,这

  • C++类和对象深入探索之分文件编写点和圆的关系详解

    目录 创建圆心类 创建圆类 判断点圆关系函数 最终实现 总结 上一篇封装直达 创建圆心类 point.h #pragma once #include<iostream> using namespace std; //创建圆心类 class Point { public: void setM_x(int x); int getM_x(); void setM_y(int y); int getM_y(); private: int m_x; int m_y; }; 把圆心的横纵坐标设为私有,公共

  • C++学习笔记之类与对象详解

    目录 前言: 1.访问限定符: [问题]C++中 struct和class的区别是什么? 2.封装 [问题]在类和对象的阶段,我们只研究类的封装特性,那什么是封装呢? 3.类的定义与声明 [问题]函数调用的问题 4.类的作用域 5.类的实例化 6.类的分类 总结 前言: 1.C 语言是面向过程的,关注的是过程,分析出求解的步骤,通过函数逐步调用解决问题. 2.C++是基于面向对象的,关注的是对象,蒋一件事情拆分成不同的对象,靠对象之间的交互完成. 举个例子:外卖系统 面向过程是下单.接单.送餐的

  • C++中的类与对象深度解析

    目录 初始化列表 引论 初始化列表 explicit关键字 引论 explicit关键字使用 static成员 友元 引论 友元 内部类 基础概念 内部类的使用 补充 析构顺序例题 总结 初始化列表 引论 //初始化列表的引出 class B { public: B() { cout << "我是B的构造函数" << endl; } private: int _b; }; class A { public: A() { cout << "我

  • C++深入讲解类与对象之OOP面向对象编程与封装

    目录 1.面向对象编程 2.面向过程性编程和面向对象编程 3.类的引入 4.类的定义 4.1类的两种定义方式 4.1.1声明和定义全部放在类体中 4.2.2.声明和定义不放在类体中 5.类的访问限定符及封装 5.1 访问限定符 5.2封装 6.类的作用域 7.类的实例化 8.类对象模型 如何计算类对象的大小 面向过程编程也叫结构化编程.虽然结构化编程的理念提高了程序的清晰度,可靠性,并且方便维护.但它再编写大型的程序时,仍然面临这巨大的挑战,OOP(面向对象编程)提供了一种新的方法.与强调算法的

  • C++深入刨析类与对象的使用

    目录 this指针 this指针存放在哪 nullptr与类 类的默认成员函数 构造函数 意义 析构函数 拷贝构造 运算符重载 this指针 现在给出一段代码,实现一个普通的日期 date 的打印: class date { public: void print() { cout << year<<' ' << month <<' '<< day << endl; } void init(int y, int m, int d) {

  • c++类和对象基本概念

    目录 什么是类? 什么是对象? 类的定义 创建对象 成员访问(初始化) 总结 什么是类? 一系列事物的抽象,对于c++而言,万事万物都可以是类. 类包括:属性+行为 属性:事物特征->数据类型描述: 行为事物的操作->函数描述: 什么是对象? 类的具体化,类的实例化,抽象->具象: 类的特点:封装.继承.派生.多态. 类的定义 创建方法:class class 类名{ //权限限定词 public: protected://保护属性 private://当不做继承时,数据成员写成私有属性

  • C++初阶教程之类和对象

    目录 类和对象<上> 1. 类的定义 2. 类的封装 2.1 访问限定修饰符 2.2 类的封装 3. 类的使用 3.1 类的作用域 3.2 类的实例化 4. 类对象的存储 5. this 指针 5.1 this 指针的定义 5.2 this 指针的特性 类和对象<中> 1. 构造函数 1.2 构造函数的定义 2.2 构造函数的特性 2. 析构函数 2.1 析构函数的定义 3. 拷贝构函数 3.1 拷贝构造函数的定义 3.2 拷贝构造函数的特性 4. 运算符重载 4.1 运算符重载的

  • C++全面精通类与对象

    目录 运算符重载 运算符复用 前置后置运算符 const C++ 的IO流 初始化列表 explicit 关键字 运算符重载 C++语法设计很巧妙,比如运算符重载一个 > bool operator>(const Date& d) { return !(*this <= d); } 这里可以结合前面的内联函数来进一步提高代码的效率,而内联函数不支持 .h 和 .cpp 分开写,所以成员函数要成为内联函数最好的办法就是把定义放在类里面,类里面定义的会被默认为是 inline 内联函

  • Java十分钟精通类 封装 继承

    目录 什么是类成员 什么是实例变量 那么实例变量和类变量的区别呢? 那么类方法和实例方法的区别? static关键字 static成员方法: static用处: 封装: 封装的概念 封装的分类 封装的使用 继承: 什么是继承 继承的使用: 方法重写的规则: super关键字: 什么是类成员 使用static修饰的成员方法和成员变量称为类成员 使用static修饰的成员变量叫做类变量 使用static修饰的成员方法叫做类方法 什么是实例变量 未使用static修饰的成员方法和成员变量称为实例成员

  • Python面向对象编程中的类和对象学习教程

    Python中一切都是对象.类提供了创建新类型对象的机制.这篇教程中,我们不谈类和面向对象的基本知识,而专注在更好地理解Python面向对象编程上.假设我们使用新风格的python类,它们继承自object父类. 定义类 class 语句可以定义一系列的属性.变量.方法,他们被该类的实例对象所共享.下面给出一个简单类定义: class Account(object): num_accounts = 0 def __init__(self, name, balance): self.name =

  • 关于JavaScript定义类和对象的几种方式

    可以看看这个例子: 复制代码 代码如下: var a = 'global'; (function () { alert(a); var a = 'local'; })(); 大家第一眼看到这个例子觉得输出结果是什么?'global'?还是'local'?其实都不是,输出的是undefined,不用迷惑,我的题外话就是为了讲这个东西的. 其实很简单,看一看JavaScript运行机制就会明白.我们可以把这种现象看做"预声明".但是如果稍微深究一下,会明白得更透彻. 这里其实涉及到对象属性

  • JavaScript定义类和对象的方法

    本文实例讲述了JavaScript定义类和对象的方法.分享给大家供大家参考.具体方法如下: 在JS中,类和对象有多种不同的写法,因为本人对JS也不怎么熟,所以就本人的理解来写,如果哪位朋友发现有不对,请告之,共同学习. JS定义一个类有两种定法(我只知道这两种): 1. 定义函数的方式: 定义: 复制代码 代码如下: function classA(a) {      this.aaa=a;  //添加一个属性      this.methodA=function(ppp)  //添加一个方法

  • javaScript中定义类或对象的五种方式总结

    第一种方式: 工厂方法 能创建并返回特定类型的对象的工厂函数(factory function). function createCar(sColor){ var oTempCar = new Object; oTempCar.color = sColor; oTempCar.showColor = function (){ alert(this.color); }; return oTempCar; } var oCar1 = createCar(); var oCar2 = createCa

  • Javascript学习笔记5 类和对象

    面向对象语言三大特点:继承,多态,封装,这三点虽然Javascript没有提供天然的语法实现,但是我们都可以通过prototype等技巧来实现,因此这种说法似乎不过分. 在Javascript中,构造对象有三种方式: 1. 首先,我们要明确一个概念,Javascript是一种弱类型的语言,一方面体现在Javascript的变量,返回类型都是没有强类型约束的,另一方面,Javascript可以为对象任意添加属性和方法.根据这个,我们可以写出这样的代码: 复制代码 代码如下: <script typ

  • Javascript创建类和对象详解

    现总结一下Javascript创建类和对象的几种方法: 1.原始的创建方法: <script type="text/javascript"> var person = new Object(); person.name="Amy"; person.sex="Woman"; person.show=function(){ document.write("name is: "+this.name+" ; se

  • js 用于检测类数组对象的函数方法

    如下所示: //判定o是否是一个类数组对象 //字符串和函数有length属性,但是它们 //可以用typeof检测将其排除.在客户端JavaScript中,DOM文本节点 //也有length属性,需要用额外判断o.nodeType!=3将其排除 function isArrayLike(o){ if(o&&//o非null/undefined等 typeof o==="object"&&//o是对象 isFinite(o.length)&&a

  • php基础知识:类与对象(5) static

    Declaring class members or methods as static makes them accessible without needing an instantiation of the class. A member declared as static can not be accessed with an instantiated class object (though a static method can).  声明静态的类变量和方法可以不需要实例化类对象的

随机推荐