C++是一门OOP(面向对象)的语言。
而C语言只是一门面向过程的语言。
里面有些思路需要重新改变。

前情提要:文章较长,请根据目录自行选择

目录

  • 一、守望先锋
  • 二、对象和类
    • 1、什么是类
      • C语言创建一个栈的”类“
      • C++创建一个栈的类
    • 2、类的定义
      • 声明与定义的区别
      • 访问限定符
      • 类大小的计算
    • 3、this指针
      • 简单的日期类
      • 成员变量与成员函数的空间分布
      • this指针的分析
      • this指针的特性
      • this指针储存在哪?
      • 看看这个,来理解
  • 三、类的六个默认成员函数
    • 1、 构造函数
      • 构造函数的概念
      • 什么?你不相信系统调用了构造函数
      • 构造函数的特性
      • 自定义类型与内置类型
      • 对于构造函数的建议
    • 2、析构函数
      • 析构函数的定义
      • 析构函数的特性
      • 多个对象,析构函数调用的顺序
    • 3、拷贝构造函数
      • 拷贝构造函数的特性
      • 为什么传值传参会引发无穷递归调用
      • 拷贝构造与传引用
      • 深拷贝与浅拷贝
    • 4、赋值运算符重载
      • 运算符重载的特性
      • 复制拷贝与拷贝构造
    • 5、对前4个默认成员函数的总结
    • 6、const成员函数
      • 特性
      • 几个问题理解const
    • 7、取地址操作符重载
      • 保护对象不被读取地址
  • 四、一个完整的日期类(选读,可跳)
    • 日期类.cpp
    • Date.cpp
    • Date.h
  • 五、友元与>>、<<运算符重载
    • 为什么cin>>、cout<<可以自动识别内置类型
      • cout
      • cin
    • 友元函数
    • 友元类
  • 六、深入了解构造函数
    • 1、函数体内初始化
    • 2、初始化列表
    • 3、为什么要多此一举准备两种初始化模式呢?
      • 初始化列表才是单个成员变量定义的阶段
      • 初始化列表初始化的顺序

一、守望先锋

天使姐姐不漂亮吗?

为一个场守望先锋街机先锋统计数据

如果是面向过程的程序员,可能会考虑:

记录每个玩家所选的英雄,击杀人数,对对面伤害量,治疗量,承受伤害量,命中率等等。

同时还要记录每个英雄的移动,释放技能,技能是否命中,技能造成的伤害的计算,甚至还要考虑技能的运动等等。
甚至还要考虑对方的技能对自己的影响,以及队友的技能对自己的影响。

用一个主函数 main 来获取所有数据。
调用另外一些函数来分别计算英雄的移动,技能伤害,技能影响,收到的伤害等等。

再调用另外一部分函数来显示结果。玩过守望先锋的玩家都知道,对于这些数据的 计算,统计,显示,都是瞬时性的,会随着玩家的操作不断变化。而且,一次组队并不是只打一场,可能会由两场左右。

守望先锋并不是传统的FPS游戏,而是FPS与Moba游戏的结合

而且,对于不同的英雄都要根据英雄的技能去计算,统计不同的数据。
如:安娜要分别统计开镜和不开镜的命中率,以及睡针的命中人数。
法拉要统计火箭弹直接命中人数等等。


而不同模式,又会有不同的变化,对英雄的移动,技能的影响也是不同的。
如:战斗狂欢,让所有英雄的血量翻倍,技能CD减半,这就会造成很多不一样的“化学反应”。
像我有一次战斗狂欢打了 1.5h 才打完(路霸一直卡车旁边,都在卡加时),对于普通快速10分钟左右可是相当长的。对于比赛的各个数据上限又该怎么设置?

而不一样的地图对角色的移动,技能的影响又是不一样的。比如:地形杀

但对于数据怎么办?又要重新统计。这相对于计算机而言都太复杂。(如果是这样,我相信网易的服务器早就崩了)。

总之,对于过程性编程,首先要考虑遵循的步骤,然后要考虑如何表示这些数据。

如果是一位OOP程序员,其不仅要考虑如何表示数据,还要考虑如何使用数据 。

我要跟踪什么呢?当然是每个玩家,因此要有一个对象来表示每个玩家的各个方面的数据。可以选择为各个不同的英雄定义不同的类,在通过类来为玩家创建对象,让计算机执行计算玩家之间的数据交互,可以自动计算。我们要做的仅仅是研究如何跟踪或表示,每个玩家之间的数据交互。
对于OOP程序员,只需为每个不同英雄定义属于他们的类,每次游戏开始,再为每个玩家根据其选择创建对象。再利用算法跟踪玩家之间的数据交互即

这不比面向过程编程简单?(不知道我理解的对不对)

ps:Dv.A爱你呦~

二、对象和类

这个世界太复杂,处理复杂性的方法之一是简化抽象
守望先锋游戏中,通过为每个英雄定义一个类,为每个玩家创建一个对象来统筹数据。
在C++中,用户定义类型指点是实现抽象接口的类的设计。
(说人话就是按照需求,定义类。

1、什么是类

我们都知道建一栋房子首先需要什么?

需要图纸,房子的结构图纸。

有了图纸,我们就可以根据图纸见很多大同小异的房子(一张图纸建出来的么)。

房子可以建很多,而图纸始终是那一张。
这样可以帮助我们更好理解。

对像是一个实体,而类只是一个自定义的类型。
就像int a;
a 是一个实体变量,在内存中有空间,而 int 只是一个类型,表明 a 的身份。

这样应该理解了吧。
基本类型(内置类型)定义变量完成了三项操作

  1. 决定数据对象需要的内存数量
  2. 决定如何解释内存在的位(long和float在内存中占用的位数相同,但是他们转换成数值的方法不同);
  3. 决定可使用数据对象执行的操作或方法。

对内置类型而言,有关信息全部被内置到编译器中。但是C++在自定义类型是,必须要自己提供所有信息。

类的实例化:就是根据类创建一个对象

C语言创建一个栈的”类“

C语言也是存在”类“。我们常用的结构体。
比如我们用C语言去是实现一个栈

#define CAP 4
typedef int STData;
typedef struct Stack//结构体用于维护栈
{int top;//栈顶标记STData* arr;//栈的指针int capacity;//栈的容量
}STstack;

这是对于栈的数据的定义,栈就是我们用结构体定义的一个”自定义类型“–”栈类型“。(因为它并不具有自定义类型的全部信息

void InitStack(STstack* st);//栈的初始化
void StackPush(STstack* st, STData n);//元素入栈
STData StackPop(STstack* st);//元素退栈
void StackExpansion(STstack* st);//扩容
int StackEmpty(STstack* st);//判断栈是否为空
void StackDestory(STstack* st);//销毁栈,防止内存泄漏
void StackPrint(STstack* st);//打印栈的元素,但前提是要退栈才能得到元素

这些函数是我们能够对栈执行的操作

但是数据和执行方法都是分离的。

这就导致我们重点关注的是对栈操作的整个过程
就是,我们这一步选择入栈,下一步选择弹栈。

C++创建一个栈的类

我们会将栈看为一个类,也就是一个类型,一个自定义类型。
而一个类型,我们需要两个部分

1,类的成员变量(类的属性)
2,类的成员函数(类的行为/能够执行的操作)

这个也满足上面关于类型的三项操作。

对于C++而言,就不再用结构体这个概念了,该叫
C++将structC语言的结构体 - 升级到 - 类

目前可以认为C++中的类就是C语言的结构体除了定义结构体成员变量还有结构体成员函数
比如:

#define CAP 4
typedef int STData;
struct Stack//结构体用于维护栈
{//结构体成员变量int top;//栈顶标记STData* arr;//栈的指针int capacity;//栈的容量//结构体成员函数void InitStack(STData capacity=4);//栈的初始化//缺省void StackPush(STData n);//元素入栈STData StackPop();//元素退栈void StackExpansion();//扩容int StackEmpty();//判断栈是否为空void StackDestory();//销毁栈,防止内存泄漏void StackPrint();//打印栈的元素,但前提是要退栈才能得到元素
};

你目前可以理解成长这样。

而且,其类名就可以是其自定义类型的类名。
直接Stack a1;。这就定义了一个对象。
而且也不用传上面指针过去了,可以a1.Init(),就可以调用那些成员函数。
由于C++兼容C语言,这可以是一个类。但C++会使用class(其中文有类的意思)。

2、类的定义

class ClassName//class是定义类的关键字 后面接一个类的名字
{//类体:由成员变量和成员函数组成
};

与定义结构体很像。
对于类而言,其中的成员函数。你可以直接在类中定义。也可以在.h文件类中声明,去.c文件中定义。但是,在类中的成员变量,那只是声明,并不是定义。

声明与定义的区别

声明:告诉编译器,我有一个这样的东西在这,但并未实现。
定义:根据声明,去实现这个对象的需求。

形象来说就是:

游戏公司建了一个新游戏的文件夹,并向外发布公告,说未来会发布一款新游戏,也可能文件夹都还没建好。
这就是声明,只是告诉你有,但你不知道游戏剧情、内容、玩法是什么。只知道有这个游戏。

当游戏发售后,你买了。你就可以知道游戏的所有内容。
这就是定义。

声明是对的是一个属性,而定义对的是一个实体。

访问限定符

C++对于类中的成员提供了访问限定符
private(私有),public(公有),protected(保护)
他们描述了对类成员的访问控制

根据类创建的对象,都可以访问到对象类的公有部分,并且只能通过公有函数来访问对象类的私有成员

访问控制,也是对对象类的数据的保护。

OOP编程的主要目标之一是隐藏数据,因此这类数据通常放在私有部分,
而组成类接口的成员函数放在公有部分,否则就无法调用这些函数。

由于C++兼容C语言,且C++对C语言的结构进行了拓展,如

struct A
{};
class A
{};

在C++中,都是类。

两者区别

对于struct创建的类,里面的成员默认公有
对于class 创建的类,里面成员默认私有

例如:创建一个OW英雄-安娜的类

class Ana
{public:void ShowData();//展示数据void Teletherapy(bool input);//远程治疗void SleepyNeedle(bool input);//睡针
private:char Name[20];//名字int Weight;//体重int Height;//身高double Speed;//移动速度double Blood;//血量//睡针类 睡针
};

私有数据,我们是不能修改或访问的,
只能通过公有函数去操作,或通过公有函数来了解私有数据的状态。

如果我们能直接访问到私有数据,那么我们也能对私有数据进行修改,但那样不就成开挂了吗?(正常人谁打竞技游戏开挂)。

所以,一般情况下,成员变量都是私有的,想给你用的成员函数是公有的,不希望被调用的函数是私有的。

类大小的计算

根据规则:计算类的大小,是不考虑类中成员函数的,只计算类中成员变量,同时还要考虑结构体内存对齐规则,也就是计算结构体大小的规则。

结构体内存对齐规则

  1. 第一个成员在与结构体偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
    VS中默认的对齐数为8
  3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是
    所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

那就计算一下

#include <iostream>
class Ana
{public:void ShowData();//展示数据void Teletherapy(bool input);//远程治疗void SleepyNeedle(bool input);//睡针
private:char Name[20];//名字int Weight;//体重int Height;//身高double Speed;//移动速度double Blood;//血量
};
int main(void)
{Ana a1;Ana a2;std::cout << "a1 = "<<sizeof(a1)<<std::endl;std::cout << "a2 = "<<sizeof(a2)<< std::endl;return 0;
}

创建了两个对象。

编译器输出结果。

来看看其内存的概念图

对于类中的成员函数是不会进行计算大小的。

3、this指针

这也是类中一个比较重要的知识点

通过一个日期类来分析(因为日期类比较简单)

简单的日期类

#include <iostream>
class Date
{public:void Init(int year = 1, int month = 1, int day = 1)//缺省参数{_year = year;_month = month;_day = day;}void print(void){std::cout << _year << "年" << _month << "月" << _day<<"日"<<std::endl;}private:int _year;int _month;int _day;
};
int main(void)
{Date d1;//类的实例化Date d2;d1.Init(2002,2,5);//对象d1初始化d2.Init(2019,4,1);//对象d2初始化d1.print();//打印d2.print();//打印
}

输出结果

在调用对象类的成员函数初始化对象 d1,d2 。
在成员函数中可以访问到对象封装的成员变量。

再介绍一下
对于类的实例化,创建对象。

 Date d1;//类的实例化Date d2;

系统会为对象d1 d2中分配空间。
但是只是对象中的成员变量分配在该对象的空间内。

成员变量与成员函数的空间分布

但是对于成员函数,其是在一个代码公共区段,而不是在每个对象空间的内部。不会每调用一次就分配一块空间。
每个对象都可以访问那个区段

this指针的分析

我们就该想,当调用函数时,进入公共区段。如何确定调用的是那个对象的成员变量呢?
其实,传递过去的参数,不仅仅有显示声明定义的参数,还有一个隐藏的this指针。
即,C++编译器会为每一个非静态成员函数配备一个隐藏的指针参数。

该指针指向当前对象(函数运行时调用该函数的对象)。

当然,你也可以这样写

void Init(int year = 1, int month = 1, int day = 1)//缺省参数
{this->_year = year;this->_month = month;this->_day = day;
}
void print(void)
{std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
}

但是我们不能自己在参数列表中加一个this指针,因为编译器自己会传递,会处理。要是自己加了,就会造成参数缺失。

这样也是允许的,不容易搞混。

成员函数调用的真实样子

d1.Init(2002, 2, 5);//->Init(&d1,2002, 2, 5);
d2.Init(2019, 4, 1);//->Init(&d2,2019, 4, 1);d1.print();//->print(&d1)
d2.print();//->print(&d2)

图片更好看一些。

注意

而且,对于类的成员变量,最好命名独特一点,不然,如果和缺省参数名字一样,编译器会无法识别,this指针指向不明,从而报错

this指针的特性

  1. this指针的类型:类型const
  2. 只能在成员函数的内部使用
  3. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
  4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

this指针储存在哪?

就我目前微薄的知识,this指针是储存在栈中的。
因为,this指针毕竟是一个形参。而形参和局部变量都是储存在栈中的。
可以随着成员函数的调用而创建函数结束就销毁。但是不同编译器是按照不同的规则的,比如VS就是通过ecx寄存器自动传递。

看看这个,来理解

class A
{
public:void PrintA() {cout<<_a<<endl;}void Show(){cout<<"Show()"<<endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->PrintA(); p->Show();
}

创建一个对象指针,并把它初始化为空。

p->PrintA();如果this->_a是行不通的,本身就是空指针,指向空,根本就不会有指向有权限的空间。算是非法访问

但是p->Show();,并未访问对象内,而是访问到类的公共区段。不会造成非法访问。

三、类的六个默认成员函数

类的默认成员函数即使我们

不自己声明定义,编译器也会自动创建定义

对于一个什么成员函数都没有的类,是一个空类。但是,当编译器处理时,会自动生成6个默认成员函数来防止出错。

事实上,真正用处大的是前4个,后面两个,基本上没多大用处。

1、 构造函数

该函数可以完成对对象的初始化。
C++提供这个默认成员函数。是为了解决,没有初始化就使用对象的问题。

对于日期类

class Date
{public://void Init(int year = 1, int month = 1, int day = 1)//缺省参数//{//  this->_year = year;//   this->_month = month;// this->_day = day;//}void print(void){std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;}private:int _year;int _month;int _day;
};

如果没有Init()函数,如果是C语言的话,就会报错,因为没有初始化。
就比如这样

int main(void)
{Date d1;Date d2;d1.print();//->print(&d1)d2.print();//->print(&d2)
}

我们没有初始化,却直接打印。事实上,编译器输出

虽然没有报错,但是却输出了随机值。这样也是防止了程序直接崩溃的问题。

构造函数的概念

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员
都有 一个合适的初始值,并且在对象的生命周期内只调用一次

这是一个默认成员函数,意思就是如果我们自己不定义,系统就会自动生成。
加上构造函数

Date(int year = 1, int month = 1, int day = 1)
{_year = year;_month = month;_day = day;
}

输出结果

如果我们自己不定义,系统会自动生成,我们定义,系统就调用我们定义的函数。

什么?你不相信系统调用了构造函数

那我就加一个打印,来看看结果

Date(int year = 1, int month = 1, int day = 1)
{std::cout << "Date()" << std::endl;_year = year;_month = month;_day = day;
}

看看输出结果:

现在相信了吧。

构造函数的特性

构造函数虽然名字感觉像是负责为对象创建分配空间,
但实际上,起作用只是完成对对象的成员变量的初始化,防止未初始化就使用而造成的程序崩溃。

特性

  1. 名字与类名一样
  2. 函数无返回值(注意:是无返回值,void是返回一个空,实际上还是有返回值,而构造函数其根本就没有返回类型)
  3. 在对象实例化时,编译器会自动调用对应的构造函数(我们没定义就调用默认的)
  4. 构造函数可以重载

虽然可以重载
但是

Date()
{std::cout << "Date()" << std::endl;_year = 10;_month = 1;_day = 1;
}
Date(int year=1, int month = 1, int day = 1)
{std::cout << "Date()" << std::endl;_year = year;_month = month;_day = day;
}

却不受欢迎的,因为编译器也不知道如何处理。会报错。

但可以这样

Date(int year,int month = 1, int day = 1)
{std::cout << "Date()" << std::endl;_year = year;_month = month;_day = day;
}

在初始化的时候,自己无论如何都要提供一个值。

或者

Date()
{std::cout << "Date()" << std::endl;_year = 10;_month = 1;_day = 1;
}
Date(int year, int month, int day)
{std::cout << "Date()" << std::endl;_year = year;_month = month;_day = day;
}

可以运行,但还不如全缺省的构造函数。

自定义类型与内置类型

内置类型:内置类型就是语法已经定义好的类型:如int/char…,
自定义类型:是我们使用class/struct/union自己定义的类型

对于普通的成员变量而言,其默认的构造函数,感觉效果并不怎样。但是,如果有一个成员变量也是类?(自定义类型)。

#include <iostream>
class A
{public:A(){std::cout << "A()" << std::endl;}
private:int a;
};
class Date
{public:void print(void){std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;}private:int _year;int _month;int _day;A a1;
};
int main(void)
{Date d1;Date d2;d1.print();//->print(&d1)d2.print();//->print(&d2)
}

其输出结果为

像上面那段代码,对于类Date,我们自己没有定义构造函数,而编译器会生成默认构造函数,取对成员变量初始化。

而对于内置类型,直接初始化一个随机值,防止程序崩溃。

但是对于自定义类型,编译去会取调用它的构造函数对自定义类型的成员变量进行初始化

我定义了A的构造函数是为了可以更好的看到自定义类型的构造函数被调用。

对于内置类型,编译器基本不会处理,
对于自定义类型,编译器会去调用自定义类型的构造函数

大多数情况下,默认构造函数都不太顶用,最好还是自己写一个满足要求的。

对于构造函数的建议

一般而言,构造函数有三种(不需要传递参数就可以调用的函数

  1. 编译器默认生成的(直接赋随机值的那种)
  2. 我们自己写的无参数的
Date()
{_year = 10;_month = 1;_day = 1;
}

3.我们自己写的全缺省的

Date(int year=1, int month=1, int day=1)
{_year = year;_month = month;_day = day;
}

不过,就我而言,还是推荐使用全缺省的构造函数
因为,这个完全可以兼容前面两种,

既可以带参,
也可以不带参,
还可以带部分参

岂不美哉!!!!!

猩猩看了都说好

2、析构函数

这个和名字其实不太一样,析构函数并不是负责销毁对象的空间,只是做一些,资源清理的工作。

就跟内置类型的对象一样,类对象的销毁都是由编译器完成的,其本身不具备这种操作。

析构函数的定义

析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。
而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。

析构函数的特性

特征

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值。不能重载。
  3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

对于日期类,额,这个析构函数基本没什么作用。但是对于栈、队列等数据结构的类还是相当有意义的。

清理资源可以防止内存泄漏等等。

比如数组栈调用结束后,析构函数就会自动调用,去销毁其数组,就不会因为我们忘记调用free()而造成内存泄漏

对于内置类型,析构函数不会起什么作用
对于自定义类型,析构函数会清理对象的资源,进行析构。

多个对象,析构函数调用的顺序

对于日期类

class Date
{public:Date(int year=1, int month=1, int day=1){_year = year;_month = month;_day = day;}void print(void){std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;}private:int _year;int _month;int _day;
};
int main(void)
{Date d1(2002);Date d2;Date d3;Date d4;d1.print();//->print(&d1)d2.print();//->print(&d2)
}

对象d1 d2 d3 d4都是通过函数调用在为对象分配空间,当其生命周期结束后,编译器就会销毁对象在栈上的空间。

但是,对象销毁的顺序是什么呢?数据结构-栈

要知道,这几个对象都是存放在栈上的。

对于操作系统中的栈与数据结构中的栈,基本没啥关系。但是两者都符合后进先出的条件

我们知道数据结构对于数据元素的处理规则是后进先出。则,对于在栈上的对象而言,也会符合这个要求。

在生命周期结束后,
先创建的对象,后调用析构函数;
后创建的对象,先调用析构函数。


析构函数调用顺序

  1. d4
  2. d3
  3. d2
  4. d1

要注意,当生命周期到的时候。

3、拷贝构造函数


回声开大:人格复制
就是在一定时间内(好像最近时间又削短了),回声可以复制敌方任意一个英雄,并拥有其所有技能(充能时间也大大缩短)。

就相当于,在这一定时间内,你所操作的英雄换了一个类对象。

或者说,
对面一个被你选中的英雄的所有对象数据被你Ctrl C + Ctrl V。完全被你拷贝过来了。

换句话说,在这一定时间内,你根据他的类,创建了一个一模一样的对象。

拷贝构造函数的特性

特性如下:

  1. 拷贝构造函数是构造函数的一个重载形式。即函数名也是类名。不过其参数列表是与对象有关。
  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用

为什么传值传参会引发无穷递归调用

正常的拷贝构造-传引用调用
对于引用的介绍请参考C++入门语法

Date(Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}
Date d1;
Date d2(d1);

如果是传值调用

Date(Date d)
{_year = d._year;_month = d._month;_day = d._day;
}

因为,
对于形参要开辟一个对象空间,再将实参对象传递过来,将实参拷贝到这个形参空间,但,又需要调用拷贝构造函数,又需要开辟一个形参空间。。。。就这样不断拷贝,不断开辟空间,无限递归。

概念图

而对于传引用调用
传递的是要拷贝的对象的别名。只是拿到了目标对象的别名,可以对其进行访问,并且,这个过程并没有再开辟空间

其实,也可以传址调用

Date(Date* d)
{_year = d->_year;_month = d->_month;_day = d->_day;
}Date d1;
Date d2(&d1);

虽然也可以达到拷贝的效果,但是,其语法上并不是拷贝构造函数,而且比较麻烦,毕竟指针这容易出错。

拷贝构造与传引用

如果是对于一个对象而言,

void test1(Date d)
{}
void test2(Date& d)
{}
test1(d1);
test2(d1);

对于test1()而言,是传值调用,要先开辟一个空间,将对象d1拷贝过去,就需要调用拷贝构造函数,
但是test2(),是传引用,直接就可以访问到源空间,根本不需要调用拷贝构造函数。这样对于效率也要快好多。

而且,拷贝构造毕竟是传引用,如果不小心会改变源空间的值,因为拷贝构造也是在类的公共区段,是在类内,不会被访问限定。
那么最好加一个const去防止源对象的成员变量被修改。

Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}

这样,哪怕写反了复制顺序,编译器自己也会报错,而不会影响程序。

深拷贝与浅拷贝

拷贝构造也是一个默认成员函数
如果我们不自己定义,编译器也会自动生成。

如果是一个日期类,类的成员变量全部都是内置类型的那种。编译器会自动生成一个拷贝构造函数,一个个直接拷贝过去,简称:值拷贝浅拷贝

#include <iostream>class A
{public:A(){std::cout << "A()" << std::endl;}
private:int a;
};
class Date
{public:Date(int year=1, int month=1, int day=1){_year = year;_month = month;_day = day;}void print(void){std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;}private:int _year;int _month;int _day;};int main(void)
{Date d1(2002);Date d2(d1);d1.print();//->print(&d1)d2.print();//->print(&d2)
}


这就是输出结果。依旧完成了拷贝构造。

但是对于一些自定义类型而言会出大问题。
比如:数组栈类

class Stack
{private:int* arr;int size;int capacoty;
};

当其对于栈对象进行拷贝构造,对这三个内置类型进行浅拷贝

这样浅拷贝完全不符合我们的要求,我们是希望通过拷贝,可以得到另一个与原对象一模一样的对象,
而这样的浅拷贝,当对对象 s1 进行改变时,对象 s2 也会受到改变。

就像

int a=10;
int b=a;

这是我们希望达到的效果。

而对,像栈、队列这一类的类,浅拷贝都无法达到正确效果,只有依靠深拷贝去完成。(嘿嘿,这个下次再完成)

而如果日期类中有自定义类型的成员变量,其会去调用自定义类型的拷贝构造函数,来完成对自定义类型的拷贝。

4、赋值运算符重载

这也是类的默认成员函数

  • 函数重载:支持定义同名函数
  • 运算符重载:为了让自定义类型可以像内置类型一样去使用运算符

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类
型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似

C++为了让自定义类型的对象也能像内置类型的对象一样进行例如+、-、*、/、=、==的一系列操作。提供了运算符重载这种特性。

运算符重载的特性

特性如下:

  1. 函数名字为:关键字operator后面接需要重载的运算符符号。
  2. 函数原型:返回值类型 operator操作符(参数列表)
  3. 不能通过连接其他符号来创建新的操作符:比如operator@
  4. 重载操作符必须有一个类类型或者枚举类型的操作数
  5. 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  6. 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的
    操作符有一个默认的形参this,限定为第一个形参

注意.* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。

像这类函数,都是定义在类中,不然,访问不到成员变量。

继续拿日期类举例

#include <iostream>class Date
{public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(Date& d){_year = d._year;_month = d._month;_day = d._day;}bool operator==(const Date& d){return (_year == d._year) && (_month == d._month) && (_day = d._day);}void print(void){std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;}private:int _year;int _month;int _day;
};
int main(void)
{Date d1(2020,1,3);Date d2(d1);std::cout << (d1 == d2) << std::endl;d1.print();//->print(&d1)d2.print();//->print(&d2)
}

在类中定义运算符重载函数,使用时,就像内置类型一样使用运算符即可。
同时,

要注意,在自定义类型使用运算符时,第一个参数会是this指向的对象。

第一个参数this指针是左操作数,第二个参数是右操作数

再来看一个数组类,有点神奇。

#include <iostream>
class Array
{public:Array(){for (int i = 0; i < 10; i++){_arr[i] = i;}_size = 10;}int& operator[](int pos){return _arr[pos];}int GetSize(void){return _size;}
private:int _arr[10];int _size;
};
int main(void)
{Array arr;for (int i = 0; i < arr.GetSize(); i++){std::cout << arr[i] << " ";}return 0;
}

这个[]运算符重载,让对象可以像数组一样访问,如果是返回引用的话
甚至可以对数组的值进行修改,就像普通数组那样操作

复制拷贝与拷贝构造

内置类型

int a=10;
int b=a;//拷贝构造
//
int a=10;
int b;
b=a;//复制拷贝

自定义类型

Date d1(2021,6,5);
Date d2(d1);//拷贝构造
//
Date d1(2021,6,5);
Date d2;
d2=d1;//复制拷贝

复制拷贝:
对象已经初始化好后,再把一个对象拷贝给另一个对象
拷贝构造:
在对象还在创建时,拿另一个同类对象去初始化这一个对象

对于自定义类型,
拷贝构造调用拷贝构造函数,
复制拷贝调用=运算符重载函数

Date& operator=(const Date& d)
{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}

为了让自定义类型能够像内置类型一样,连续赋值,与防止自己给自己赋值

a=b=c;

毕竟,赋值运算符重载函数是默认成员函数,即使我们不写,编译器也会自动生成。

#include <iostream>class Date
{public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(Date& d){_year = d._year;_month = d._month;_day = d._day;}bool operator==(const Date& d){return (_year == d._year) && (_month == d._month) && (_day = d._day);}void print(void){std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;}private:int _year;int _month;int _day;
};
int main(void)
{Date d1(2020,1,3);Date d2;d2=d1;d1.print();//->print(&d1)d2.print();//->print(&d2)
}

输出结果

对于内置类型的复制拷贝,编译器会通过默认的赋值运算符重载完成浅拷贝。

对于自定义类型的复制拷贝,编译器会调用自定义类型的赋值运算符重载完成拷贝。

5、对前4个默认成员函数的总结

  1. 构造函数:完成对象的初始化,但大部分情况下,还是需要我们自己去定义构造函数
  2. 析构函数:在对像生命周期结束时,对对象的资源进行清理
  3. 拷贝构造函数:构造函数的重载,其中一个对象还未初始化,完成对对象之间的拷贝(深/浅拷贝)
  4. 赋值运算符重载:也是拷贝行为,但是是基于两个对象已经被初始化的情况下。

对于构造和析构的特性是类似的,
对于内置类型,我们不写,编译器基本不会处理,
而自定义类型,编译器会调用自定义类型的析构和构造

对于拷贝构造和赋值运算符重载的特性是类似的,
对于内置类型,编译器会进行浅拷贝(直接赋值),
对于自定义类型,编译器要调用自定义类型的拷贝构造和赋值运算符重载,进行深拷贝。

6、const成员函数

特性

将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,
实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

注意:只有成员函数才能加const。如构造,析构等等都不能加

就是当我们确幸该成员函数中不会,也不会造成对象的任何成员变量被修改
就使用const去修饰成员函数,这样也是为了保险,防止错误操作。
比如:

bool operator==(const Date& d)const{return (_year == d._year) && (_month == d._month) && (_day = d._day);}

几个问题理解const

  1. const对象可以调用非const成员函数吗?
  2. 非const对象可以调用const成员函数吗?
  3. const成员函数内可以调用其它的非const成员函数吗?
  4. 非const成员函数内可以调用其它的const成员函数吗?

const修饰后,对成员函数或者对象所能操作的权限就被缩小,而且,权限不能放大,且不能有放大权限的可能
对于题目

  1. 不可以,因为非const成员函数中其this指针没有const修饰,也就存在放大权限的可能,可能会对this指针指向的成员变量进行修改。
  2. 可以,这属于权限缩小,而缩小权限,是不会报错的。
  3. 不可以,本身this指针就是const,当调用非const成员函数后,其隐含的this指针未被const修饰,就存在权限放大的可能。
  4. 可以,this指针在const成员函数中被const修饰,属于权限缩小。

7、取地址操作符重载

class Date
{
public :Date* operator&(){return this ;}const Date* operator&()const{return this ;}
private :int _year ; // 年int _month ; // 月int _day ; // 日}

这个操作符基本没什么那啥价值,根本不需要我们去实现它,默认的成员函数就完全够用了。

保护对象不被读取地址

但是,如果你不希望对象的地址被读出来,要对其进行保护

class Date
{
public :Date* operator&(){return nullptr ;}const Date* operator&()const{return nullptr ;}
private :int _year ; // 年int _month ; // 月int _day ; // 日}

这样,当别人想读对象的地址时,就只能读出00000000。可以对你的对象进行保护。

四、一个完整的日期类(选读,可跳)

直接看代码,这里面是学会对运算符重载的应用,本身没什么算法知识。

日期类.cpp

#include "date.h"
void test(void)
{Date d1(-11,5,26);Date d2(2025,1,1);d1.print();//date d3= d1 + 4;//->operator(&d2,4) //d3 = d1 + 3;//->operator(&d2,4)d3.print();//date d3 = d1 - 10;//d1.print();//d2.print();d3.print();//int days = d1 - d2;//std::cout << days;Date d3=d2++;d3.print();d2.print();if (d3 >= d2){std::cout << "d3<=d2" << std::endl;}//d1 += 4;//->operator+=(&d1,4)//d1 += 1;/*d1.print();d2.print();d3.print();*/
}
int main(void)
{test();}

Date.cpp

#include "Date.h"
inline int MonthDays(int year, int month)
{static int DayArrary[] = { 31,28,31,30,31,30,31,31,30,31,30,31 };int day = DayArrary[month - 1];if (month == 2 && (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0)){day = 29;}return day;
}
Date::Date(int year, int month, int day)
{if (year != 0 && month <= 12 && month > 0 && day <= MonthDays(year, month) && day > 0){_year = year;_month = month;_day = day;}else{std::cout << "date illegal" << std::endl;assert(false);}
}
void Date::print(void)
{if (_year >= 1)std::cout << "公元 ";elsestd::cout << "公元前 ";std::cout << _year << "年" << _month << "月" << _day<<"日"<<std::endl;
}
Date::Date(const Date& d)//拷贝构造
{_year =d._year;_month = d._month;_day = d._day;
}
Date& Date::operator+=(int day)//+=运算赋重载
{_day += day;while (_day > MonthDays(_year, _month)){_day -= MonthDays(_year, _month);_month++;if (_month > 12){_year++;if (_year == 0)_year = 1;_month = 1;}}return *this;
}
Date Date::operator+(int day)//+运算符重载
{//建立一个临时对象Date tmp(_year,_month,_day);//tmp += day;//->operator+=(&tmp,day);//对这个临时对象的操作return tmp;
}
Date& Date::operator-=(int day)//-运算符重载
{_day -= day;while (_day<=0)//终止条件->>_day > 0 && _day <= MonthDays(_year, _month){_month--;if (_month < 1){_year--;if (_year == 0)_year = -1;_month = 12;}_day += MonthDays(_year, _month);}return *this;
}
Date Date::operator-(int day)//-运算符重载
{Date tmp = (*this);tmp -= day;return tmp;
}//日期之间的天数int YearDays(int year)
{if (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0)return 366;return 365;
}
int Date::operator-(Date& d)//日期减日期
{int Days = 0;Date bigDate;Date smallDate;if (_year > d._year){bigDate = *this;smallDate = d;}else if (_year < d._year){bigDate = d;smallDate = *this;}else//同一年{//小月的剩余天数加上中间月份的天数,再加上大月的天数if (_month > d._month){bigDate = *this;smallDate = d;}else if (_month < d._month){bigDate = d;smallDate = *this;}else//同一个月{int tmp = abs(_day - d._day);return tmp;}}int yearTmp = bigDate._year - smallDate._year - 1;int monthTmp = bigDate._month - smallDate._month - 1;for (int i = 1; i <= yearTmp; ++i){Days += YearDays(smallDate._year + i);}Days += MonthDays(smallDate._year, smallDate._month) - smallDate._day;for (int i = smallDate._month + 1; i <= 12; i++)Days += MonthDays(smallDate._year, i);for(int i=1;i<bigDate._month;i++)Days+= MonthDays(smallDate._year, i);Days += bigDate._day;return Days;
}Date& Date::operator++()//前置++
{*this += 1;return *this;
}
Date Date::operator++(int) 后置++
{Date tmp(*this);*this += 1;return tmp;
}
Date Date::operator--(int)// 后置--
{Date tmp(*this);--* this;return tmp;
}
Date& Date::operator--()// 前置--
{*this -= 1;return *this;
}
// >=运算符重载
bool Date::operator>=(const Date& d)
{if (_year > d._year)return 1;else if(_year<d._year)return 0;else{if (_month > d._month)return 1;else if (_month < d._month)return 0;else{if (_day > d._day)return 1;else if (_day < d._day)return 0;elsereturn 1;}}
}// <运算符重载
bool Date::operator<(const Date& d)
{if (_year < d._year)return 1;else if (_year > d._year)return 0;else{if (_month < d._month)return 1;else if (_month > d._month)return 0;else{if (_day < d._day)return 1;else if (_day > d._day)return 0;elsereturn 0;}}
}
// <=运算符重载
bool Date::operator<=(const Date& d)
{if (_year < d._year)return 1;else if (_year > d._year)return 0;else{if (_month < d._month)return 1;else if (_month > d._month)return 0;else{if (_day < d._day)return 1;else if (_day > d._day)return 0;elsereturn 1;}}}
// !=运算符重载
bool Date::operator!=(const Date& d)
{if (_year == d._year && _month == d._month && _day == d._day)return 0;return 1;
}

这么长?源氏看来都要拔刀了

Date.h

#pragma once
#include <iostream>
#include <cassert>
class Date
{public://构造函数Date(int year = 1, int month = 1, int day = 1);Date(const Date& d);//拷贝构造Date& operator+=(int day);//+=运算赋重载Date operator+(int day);//+运算符重载Date& operator-=(int day);//-=运算符重载Date operator-(int day);//-运算符重载int operator-(Date& d);//日期减日期Date& operator++();//前置++Date operator++(int); 后置++Date operator--(int);// 后置--Date& operator--();// 前置--// >运算符重载bool operator>(const Date& d);// ==运算符重载bool operator==(const Date& d);// >=运算符重载bool operator>=(const Date& d);// <运算符重载bool operator<(const Date& d);// <=运算符重载bool operator<=(const Date& d);// !=运算符重载bool operator!=(const Date& d);void print(void);
private:int _year;int _month;int _day;
};

实现>,<之类的运算符重载过于琐碎,大家小心观看,因为我当时的顺序不一样,写多了。

五、友元与>>、<<运算符重载

为什么cin>>、cout<<可以自动识别内置类型

因为,在库中早已经重载好了,并且可以自动识别类型,各个函数之间构成重载。

cout


我以我LZ的英文水平翻译看看。cout是一个ostream类的对象。可以通过操作符<<将格式化或未格式化的数据通过成员函数写入到。。。。我就不知道咋翻了。

cin


很明显,cin是一个istream的类的对象。通过<<与成员函数完成对输入流的写入。
这些都不重要。大概了解是啥就行。

cout是根据ostream创建的一个对象
cin是根据istream创建的一个对象

这样,我们可以完成对自定义类型简化为内置类型.

ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << d._month << d._day;return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{_cin >> d._year >> d._month >> d._day;return _cin;
}

按照这样的形式,我们可以做到像内置类型一样直接输入。

如果,要定义在类中,我们的coutcin都是操作符的左边,导致,this指针就无法指向类中的成员变量。
所以定义在类中并不合适。只能定义在类外。

但是,在类外,我们就无法访问到类中的成员变量。
所以,引入一个友元

友元函数

提供友元函数。

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

class Date
{public:friend ostream& operator<<(ostream& _cout, const Date& d);friend istream& operator>>(istream& _cin, Date& d);Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(Date& d){_year = d._year;_month = d._month;_day = d._day;}bool operator==(const Date& d){return (_year == d._year) && (_month == d._month) && (_day = d._day);}void print(void){std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;}private:int _year;int _month;int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << d._month << d._day;return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{_cin >> d._year >> d._month >> d._day;return _cin;
}
int main()
{Date d;cin>>d;cout<<d<<endl;return 0;}

这样,友元可以突破封装,相当于开了一个后门。

注意

  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数
  5. 友元函数的调用与普通函数的调用和原理相同

友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
例如:

class Time;//时间类前置声明
class Date
{public:friend class Time;//声明时间类是日期类的友元Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(Date& d){_year = d._year;_month = d._month;_day = d._day;}bool operator==(const Date& d){return (_year == d._year) && (_month == d._month) && (_day = d._day);}void print(void){std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;}private:int _year;int _month;int _day;
};
class Time
{public:Time(int second = 0, int minute = 0, int hour = 1){_second = second;_minute = minute;_hour = hour;}
private:int _second;int _minute;int _hour;
};

时间类是日期类的友元,则,时间类中的所有成员函数都可以说日期类的友元函数。
则,在时间类中可以访问到日期类在的私有成员变量
但是,日期类依旧不能访问到时间类中的私有成员变量。

总结:

  1. 友元关系是单向的不具有交换性。(如上)
  2. 友元关系不能传递
    如果B是A的友元,C是B的友元,则不能说明C时A的友元。

六、深入了解构造函数

在创建对象的时候,对象通过调用构造函数来初始化对象。
C++在构造函数上提供了两种方案来初始化成员变量。

1、函数体内初始化

就是我们常见的初始化的模式

class Date
{public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};

2、初始化列表

class Date
{public:Date(int year = 1, int month = 1, int day = 1):_year(year),_month(month),_day(day){}
private:int _year;int _month;int _day;
};

此模式的初始化规则:

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式

3、为什么要多此一举准备两种初始化模式呢?

事实上,虽然函数体内调用构造函数后,对象中已经有了一个初始值,但是不能将其称作为类对象成员的初始化,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
例如:

初始化列表两次初始化,编译器直接报错,因为在列表中初始化只能初始化一次。但是,

编译器却可以允许,
来看看结果

这已经不叫初始化了,而是叫赋值,初始化毕竟只能初始化一次。
这也再一次证明了,函数体内“初始化”,准确来叫应该是赋值,而非初始化。
初始化列表才是真正的初始化成员变量
从而可以推出,创建一个对象,对于对象而言,其中的成员变量是在初始化列表的时候定义的

初始化列表才是单个成员变量定义的阶段

而且,对于有些成员变量,只有在初始化的时候才能赋初值,且之后就不能修改了。
例如

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

对于没有要求一定要在定义的时候初始化的成员变量,可以在函数体内”初始化“。

例如这样

class Date
{public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day),i(10),ret(_year){_year = 100;_year = 200;}
private:int _year;int _month;int _day;const int i;int& ret;
};

对于第三种,没有默认构造函数的自定义成员变量
也就是说,自定义类中没有不需要传参就能初始化的构造函数。也就是说,忘记传参就会导致报错。
这样将自定义成员变量,显示定义到初始化列表中,可以我i自定义成员变量初始化,还不会报错。

class Date
{public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day),i(10),ret(_year),t(0){_year = 100;_year = 200;}
private:int _year;int _month;int _day;const int i;int& ret;Time t;
};
class Time
{public:Time(int t){_t = t;}
private:int _t;
};

如果不显示初始化列表,编译器就会报错没有默认的构造函数

还有就是注意,初始化列表是成员函数定义的阶段,无论是哪种“初始化”模式,最终都会经历初始化列表这个阶段
所以还是推荐使用初始化列表,保险一点

初始化列表初始化的顺序

成员变量在类中声明次序就是其在初始化列表中的初始化顺序与其在初始化列表中的先后次序无关

这个知识点有点绕,但还蛮重要的。

学会了吗?不会?死神手把手教你,他很会绕的

文章关于类与对象的知识比较零碎,又比较多,还比较重要。其实更多是对我自己的总结。

看到这也挺不容易的。

在【守望先锋】学习C++的类与对象相关推荐

  1. Python学习笔记 (类与对象)

    Python学习笔记 (类与对象) 1.类与对象 面向对象编程语言类: 一个模板, (人类)-是一个抽象的, 没有实体的对象: (eg: 张三, 李四) 属性: (表示这类东西的特征, 眼睛, 嘴巴, ...

  2. Java学习笔记_类和对象

    ·类:是一组相关属性和行为的集合,可以看成是一类事务的模板,使用事物的属性特征和行为特征来描述该类事物. ·属性:该事物的状态信息 ·行为:就是该事物能够做什么 ·对象:一类事物的具体体现,对象是类的 ...

  3. python创建person类用printinfo方法_Python学习期刊Day11类和对象(2),日记,与,下

    今天是2020年2月20日,晴,6~14℃ 一.继承 继承是一种创建新类的方式.新建类将获得原有类的所有属性和方法.原有的类称为父类(超类),新建的类称为子类. Python中继承的方式如下: ''' ...

  4. python学习之【类和对象】

    前言 五一快乐! 上一篇文章python学习--[第八弹]中,给大家介绍了python中的函数,这篇文章接着学习python中的类和对象. 我们知道,python中一切皆对象.在这篇文章开始之前,我们 ...

  5. 一个编程小白的Java SE学习日志 V——类、对象【极客BOY-米奇】

    文章目录 前言(转载请说明作者!)3.18~3.25编写 观看前自问 面向对象实现过程 面向对象三大特性 封装 继承 多态 如何理解类与对象(实例)的关系? 现实理解 如何理解对象? 现实理解 任务导 ...

  6. 冰冰学习笔记:类与对象(上)

    欢迎各位大佬光临本文章!!! 还请各位大佬提出宝贵的意见,如发现文章错误请联系冰冰,冰冰一定会虚心接受,及时改正. 本系列文章为冰冰学习编程的学习笔记,如果对您也有帮助,还请各位大佬.帅哥.美女点点支 ...

  7. 冰冰学习笔记:类与对象(下)

    欢迎各位大佬光临本文章!!! 还请各位大佬提出宝贵的意见,如发现文章错误请联系冰冰,冰冰一定会虚心接受,及时改正. 本系列文章为冰冰学习编程的学习笔记,如果对您也有帮助,还请各位大佬.帅哥.美女点点支 ...

  8. python学习day2:类与对象

    类与对象 一.内置函数 1.int方法 (1)def__add__(self, y):       #两数相加 """ x.__add__(y) <==> x ...

  9. unity让对象作为参数_C#+Unity学习笔记:类与对象

    参考文献 蜜酒厅通讯社 游戏部 石中居士 对象(object):有状态.行为和身份的东西. 状态(state):表示物体特征的信息,可以用来跟踪对象的状态. 属性(properties):因为编程人员 ...

  10. Python学习9 面向对象 类和对象

    面向对象和面向过程 类和对象 类的设计 类的创建 self:相当于当前对象:类似于Java中的this 类的创建和使用: #类的命名:每个单词首字母大写 class Dog:#属性name = 'do ...

最新文章

  1. unordered_map的使用特性
  2. 全民直播CTO张云龙:已全面迁移至阿里云
  3. 如何管理和记录 SSIS 各个 Task 的开始执行时间和结束时间以及 Task 中添加|删除|修改的记录数...
  4. DAY3-“忙里偷闲”找你玩耍2018-1-11
  5. OTSU_图像二值化分割阈值的算法
  6. 19. 顺时针打印矩阵
  7. java聊天室源工程文件_socket实现java聊天室,公告等功能,前后端分离(附源码)...
  8. 基于Extjs的web表单设计器
  9. C# 类和结构的成员
  10. 下载OruxMaps离线高清卫星地图
  11. come的喂abc是什么梗
  12. 淘宝API item_search_similar - 搜索相似的商品
  13. 多线程模拟龟兔赛跑游戏
  14. 与心态相关的写作储备
  15. 伍迷随想冷饭集 之 北国冬天之随想
  16. 制作WIN10U盘启动盘
  17. php增加sqlserver扩展
  18. 计算机组装职高必背,(完整word版)职高计算机组装与维护教案.doc
  19. Ubuntu18.04+ros-melodic (包括Ubuntu16.04+ros-kinetic)乐视奥比中光相机在nano、tx2、PC等设备上的安装与使用,并解决无法显示rgb信息的问题
  20. 求答案 ? 一筐鸡蛋: 1个1个拿,正好拿完。 2个2个拿,还剩1个。 3个3个拿,正好拿完。 4个4个拿,还剩1个。 5个5个拿,还差1个。 6个6个拿,还剩3个。 7个7个拿,还剩4个。 8个8个

热门文章

  1. cocos creator后台切入切出
  2. Linux查看cuda版本
  3. mysql as用法_数据库 as 用法
  4. 图灵大会圆满落幕,码隆科技与全球最强大脑同台
  5. 计算机打字声音,键盘打字声音在哪设置
  6. win10分辨率不能调整_三国志:游戏在win10系统无法运行咋办?
  7. iOS 热敏打印机打印位图
  8. [web开发] Vue+Spring Boot 上海大学预约系统开发记录
  9. 路由备份和路由聚合综合实验
  10. win10计算机加域步骤,Windows域是什么|win10系统加入域的详细步骤