【C++修炼之路 第二章:类和对象 】下

在这里插入图片描述



1、再谈构造函数

首先先讲清楚 什么是真正的 默认构造函数?

默认构造是指:不用传参的构造函数(包括:无参构造、全缺省构造函数、自动生成的构造)

不具备默认构造:比如你自己写了一个 半缺省构造函数(刚好规避调默认构造函数的所有特点)

当类中的自定义类型不具备默认构造,这个类本身也无法给他生成默认构造


1.1 构造函数体赋值

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值

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

    private:
    int _year;
    int _month;
    int _day;
};

虽然上述构造函数调用之后,使对象中有了一个初始值,但是不能将其称为对对象中成员变量的初始化, 构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体 内可以多次赋值

总结:

初始化概念:只能初始化一次

构造函数:可以多次赋值

因此构造函数第一次赋值,不能算作初始化(性质不一样)

真正的对象初始化:使用初始化列表



1.2 初始化列表

初始化 自定义类型,自定义类型会使用自己的构造函数进行赋值,内置类型直接赋值

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

注意书写格式:冒号起手,逗号分隔

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

private:
	int _year;
	int _month;
	int _day;
};

初始化列表==本质可以理解为每个对象中成员定义的地方==,成员声明的地方是在 写类型和变量名的地方(为什么,问就是定义,别问(doge))

1.2.1 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次

1.2.2 类中包含以下成员,必须放在初始化列表位置进行初始化:
  • 引用成员变量
  • const成员变量
  • 自定义类型成员(且该类没有默认构造函数时)

引用 和 const 都必须在定义的时候初始化,因此都必须写在初始化列表中(初始化列表==本质可以理解为每个对象中成员定义的地方==)

class Stack {
public:
	// ....
	Stack(int n) {}; // 带参无缺省构造函数(不是默认构造函数)
};

class MyQueue {
public:
	// 构造函数
	MyQueue(int n, int& d)
		:_pushSt(n)  // 自定义类型(没有默认构造函数的)
		, _popSt(n)
		,tmp1(n)     // const 类型
		,tmp2(d)    // 引用 类型
		, top(0)   
	{}

private:
	// 没有默认构造的两个自定义类型
	Stack _pushSt;
	Stack _popSt;
	const int tmp1;   // const 类型
	int& tmp2;     // 引用 类型
	int top;
};

int main()
{
	int d;
	MyQueue q(10, d);
	return 0;
}

1.2.3 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使 用初始化列表初始化(这个是优先进行的,那你还不如直接使用这个)。

⭐成员变量的缺省值是为 初始化列表服务

​ 当你没有显式写初始化列表时,就会使用缺省值初始化(就跟函数参数处的缺省值一样,你不传参,就使用缺省值,这里也同理,成员变量的缺省值和初始化列表匹配)

初始化列表的传递的初始化值是比较自由的,就像函数传参一样

可以传一个函数返回值、传malloc开的空间……

class MyQueue {
public:
    // 构造函数
    MyQueue(int n, int& d)
        :a((int*)malloc(sizeof(int) * 8))     // 传malloc开的空间
        ,tmp(func())    // 传一个函数返回值
        {}
private:
    int *a;   // const 类型
    int tmp;     // 引用 类型
};

总结的精华:

初始化列表,不管你写不写,每个成员变量都会先走一遍

  • 自定义类型的成员会调用默认构造(没有默认构造就编译报错)
  • 内置类型:你初始化列表处初始化了就没事,如果不写初始化列表,则有缺省值用缺省值,没有的话,不确定,要看编译器,有的编译器会处理,有的不会处理
  • 先走初始化列表 +再走函数体
  • 实践中,尽可能的使用初始化列表初始化,不方便再使用函数体初始化

不方便的情况一般是需要对初始值进一步处理的:如将 malloc 的空间都初始化成 0

class MyQueue {
public:
    // 构造函数
    MyQueue(int n, int& d)
        :_a((int*)malloc(sizeof(int) * 8))     // 传malloc开的空间
        ,_tmp(func())    // 传一个函数返回值
        {
            malloc(__a, 0, sizeof(int) * 8));  // 空间中数值都初始化成 1
        }
private:
    int *_a;   // const 类型
    int _tmp;     // 引用 类型
};

const 是在定义的时候 赋初始值,在定义之后就不能改了

因此在初始化列表处,const 定义赋初值

同时,const 类型的变量,要么直接在初始化列表定义赋初值,要么给缺省值(这个本质也是为初始化列表服务的)


1.2.4 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关(很容易错的一点,笔试题)
class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}

	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};

int main() {
	A aa(1);
	aa.Print();
}
 
A. 输出1 1
B.程序崩溃
C.编译不通过
D.输出1 随机值

其中的本质是:声明是按顺序存储在内存中的,则执行的初始化顺序也就按照声明的顺序进行

(这里涉及到 对象模型 这个知识点)

本题中:按照 private中成员变量的声明次序,初始化列表处先走 _ a2(_ a1) 再走 _ a1(a)

因此,建议 初始化列表的定义 的顺序和 声明保持一致,否则容易出错


1.3 自定义类型的 隐式类型转换

类和对象的 隐式类型转换

先将 数值 转换成临时变量,在将临时对象拷贝给 目标对象

这个就类似 int 强转 成 double

原理一样,且都要考虑 类型转换会产生临时变量

class A {
public:
	// 单参数构造函数
	A(int x)
		:_a(x)
	{}
private:
	int _a;
};

int main()
{
	A a1(10);
	// 拷贝构造
	A a2 = a1;

	// 隐式类型转换
	A a3 = 30;   // 这个不是直接作为参数赋值,而直接就是将 int 的 30 强制类型转换成 自定义类型
	const A& a4 = 30;  // 强转生成临时变量具有常性,加个const
	// 这里为什么涉及强转?因为引用必须同类型,不同类型引用就要强转
	

	return 0;
}
 

#### 1.3.1 强制类型转换的具体过程解释:

这个是先将 int 类型的 30 构造成 一个A类型的临时对象,再将 临时对象 拷贝给目标对象

其中 先调用一次 构造函数,再调用一次 拷贝构造

同时,编译器遇到这种在一个表达式中连续的 构造函数+拷贝构造 :会优化成一次 的直接构造

强转成自定义类型,语法逻辑层面就是 构造函数+拷贝构造 ,但编译器优化了


1.3.2 强转成自定义类型,编译器的优化有什么用?或者说有什么应用场景

下面这段代码:将 类A的一个对象 Push 进 类Stack 中,要将 对象A a1 先创建出来(需要调用构造函数),再作为参数传到 Stack的函数(需要调用拷贝构造)

class A {
public:
	A(const int x)
		:_a(x)
	{
		cout << "构造函数" << '\n';
	}
	A(const A& aa) {
		cout << "拷贝构造函数" << '\n';
		_a = aa._a;
	}
private:
	int _a;
};

class Stack {
public:
	void Push(A a) {
		// ...
	}
};

int main()
{
	Stack st;
	A a1(1);
	st.Push(a1);
	return 0;
}

优化方案:

1、Push 函数的参数使用 引用类型,可以省去一次 拷贝构造

void Push(A& a)

2、也可以直接根据 “内置类型强转成自定义类型” 的思路,改变一下传值思路

const A& a = 30;

改变一下:直接传常数,Push 函数使用 const A& 类型接收,相当于 一个 int 类型强转成 内置类型

加上 const :权限可以缩小不能放大

void Push(const A& a)

st.Push(2);

这样会被编译器优化:提高了效率,同时书写便利性和可读性更高了

这种优化的写法就是学习的 STL 这个写法的原理:

vector<string>v;
string s = "11111";
v.push_back(s);
v.push_back("11111");

1.3.3 单参数构造函数 和 多参数构造函数

构造函数不仅可以构造与初始化对象,对于 “接收单个参数的构造函数”(有三种),还具有类型转换的作用。


(1) 接收单个参数的构造函数具体表现:

1、构造函数只有一个参数 :单参数构造函数

2、构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值:首位无缺省的半缺省函数

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

3、全缺省构造函数

上面用到的例子就是使用了 单参数构造函数

将 int 类型的数据传给 单参数构造函数 隐式类型转换成类类型

class A {
public:
	// 单参数构造函数
	A(int x)
		:_a(x)
	{}
private:
	int _a;
};


(2)多参数的构造函数

也可以隐式类型转换:用花括号传值

class A {
public:
	// 多参数构造函数
	A(const int x, const int y)
		:_a(x)
		,_b(y)
	{
		cout << "构造函数" << '\n';
	}
	A(const A& aa) {
		cout << "拷贝构造函数" << '\n';
		_a = aa._a;
	}
private:
	int _a;
	int _b;
};

class Stack {
public:
	void Push(const A& a) {
		// ...
	}
};


int main()
{
	Stack st;
	A a1(1, 2);
	//A a2 = (1, 2); // 不可以直接写括号来传 2 个参数
	A a2 = {1, 2}; // 可以这样写,传两个参数,C++11 支持,C++98以前的版本不支持
	// 这个原理也是隐式类型转换

	return 0;
}

同时函数也是要 const:避免权限放大

const A& a3 = { 1, 2 };

因此就可以这样写:

st.Push(a1);
st.Push({1, 2});

总结:单参数用括号,多参数用花括号

学了初始化列表 和 单参数多参数的隐式类型转换:成员变量处可以玩花的了(doge)

class A {
public:
	// 单参构造函数
	A(const int x)
		:_a(x)
	{
		cout << "单参构造函数" << '\n';
	}
	// 多参数构造函数
	A(const int x, const int y)
		:_a(x)
		,_b(y)
	{
		cout << "多参构造函数" << '\n';
	}
	A(const A& aa) {
		cout << "拷贝构造函数" << '\n';
		_a = aa._a;
	}
private:
	int _a;
	int _b;
};



class B
{
public:
	B (int x = 1){}
private:
	// 声明给缺省值是为了 初始化列表服务
	int _b = 1;
	int* p = (int*)malloc(sizeof(int) * 8); 
	A _a1 = 1;   // 隐式类型转换:单参数
	A _a2 = { 2, 3 };  // 隐式类型转换:多参数
	A _a3 = _a2;
};


int main()
{
	B b1;
	return 0;
}

1.4 explicit关键字

如果你不想让构造函数 支持隐式类型转换,需要在 构造函数 函数名前面加上 explicit 关键字

explicit Date(int year)
 	:_year(year)
 	{}



2、static成员


### 2.1 概念

声明为 static 的类成员称为类的静态成员,用 static 修饰的成员变量,称之为静态成员变量;用static修饰的 成员函数,称之为静态成员函数。

静态成员变量一定要在类外进行初始化,只声明不定义,会报错

class A
{
public:
	A(int x = 1) {}
private:
	int _a = 1;
	int _b = 1;
	static int _c; // 声明
};
int A::_c = 10;  // 类外定义

2.2 类的静态成员能不能给缺省值?

class A
{
public:
	A(int x = 1) {}
private:
	int _a = 1;
	int _b = 1;
	static int _c = ?; // 能不能给缺省值?
};

不能:结合之前的知识点

第一、类的静态成员 不存在于 该对象中,而是在静态区,只有成员变量存储于对象中

第二、成员变量的缺省值是给初始化列表的,初始化列表适用于初始化对象中的成员变量的

类的静态成员都不存在于对象中,当然不能给缺省值(即不会进行初始化列表初始化的步骤),需要自己定义


2.3 静态成员变量一个重要的性质

静态成员变量 属于整个类,属于所有对象!

这句话如何理解?

就是这个类的所有对象,都能控制同一个 静态成员变量

例如下面这段代码的运行结果为 3

class A
{
public:
	A() {  };
	static int cnt;
};
int A::cnt = 0;

int main()
{
	A a1, a2, a3;
	a1.cnt++;
	a2.cnt++;
	a3.cnt++;

	cout << a1.cnt << '\n';
	return 0;
}

你可以当作, 静态成员变量是共享的

这里发现,由于需要访问 静态成员cnt,所以置为公有,但是一般成员变量是私有的,如何处理?

可以使用函数,获取私有成员

这里使用 静态成员函数

class A
{
public:
	A() {
		++_scount; 
	}
	A(const A& t) { 
		++_scount; 
	}
    // 静态成员函数
	static int GetCnt() { 
		return cnt; 
	}
private:
	static int cnt;
};

静态成员函数一条重要的性质:没有 this 指针,只能访问静态成员

成员变量都依靠 this 指针访问的,没有 this 指针,只能访问静态成员

使用时不用传 this 指针,直接”暴力“用就好

A a1;
// 传this指针的两种情况
a.Func();
(&a)->Func();

使用时不用传 this 指针

cout << A::GetCnt() << endl;

2.4 面试题:实现一个类,计算程序中创建出了多少个这个类的对象。(根据静态成员变量的性质)

解题思路:

调用一次 构造函数或拷贝构造 算作创建一个对象,调用一次析构函数算作销毁一个对象

这里要想计算总体的数值,就必须使用 静态成员变量(所有对象共享的,则当每个对象创建时,都可以被 ”同一个“ 静态成员变量 计数)

class A
{
public:
	A() {
		++_scount; 
	}
	A(const A& t) { 
		++_scount; 
	}
	~A() { 
		--_scount; 
	}
	static int GetACount() { 
		return _scount; 
	}
private:
	static int _scount;
};

int A::_scount = 0;

void TestA()
{
	A a1, a2;  // 两次构造
	A a3(a1);  // 一次拷贝构造
	cout << A::GetACount() << endl;
}

int main()
{
	TestA();
	return 0;
}

2.5 面试题:为什么明面上创建了 4 个对象,但是 计数变量的值为 5 ?

想理解这一道题,需要先理解上一道题

class A
{
public:
	A() {
		++_scount; 
	}
	A(const A& t) { 
		++_scount; 
	}
	static int GetACount() { 
		return _scount; 
	}
private:
	static int _scount;
};

int A::_scount = 0;

A func() {
	A a;// 一次构造
	return a; // 答案:这里会生成临时对象,一次拷贝构造
}

int main()
{
	A a1, a2;  // 两次构造
	A a3(a1);  // 一次拷贝构造
	func();
	cout << A::GetACount() << endl;
}

答案:函数 func 是传值返回,会调用拷贝构造函数,生成临时对象

注意:若你将本段代码放入你的编译器,可能结果就是 4,这样可能因为你的编译器的版本较高,编译器自己优化了,但语法层面,这里就是 5

这也是为什么 航哥刚开始推荐使用 VS2019 不推荐 VS2022,是因为VS2022 优化功能太强大了,不利于学习使用


2.6 特性总结

1、静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区

2、静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明

3、类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问

4、静态成员函数没有隐藏的this指针,不能访问任何非静态成员

5、静态成员也是类的成员,受 public、protected、private 访问限定符的限制


2.7 一道 OJ题

1、【牛客】JZ64 求1+2+3+…+n

在这里插入图片描述

class Sum{
public:
    Sum(){
        _cnt += _i;
        _i++;
    }
    static int GetCnt(){
        return _cnt;
    }
private:
    static int _cnt;
    static int _i;
};
int Sum::_cnt = 0;
int Sum::_i = 1;

class Solution {
public:
    int Sum_Solution(int n) {
        Sum arr[n]; //使用变长数组
        return Sum::GetCnt();
    }
};

思路:使用变长数组

当该类类型的数组定义出来时,就已经调用了 n 次 构造函数,因此可以在构造函数处下手

变长数组是 C99 标准,VS上可能不能用

本题在实际中没有任何意义,就是用于出题

这更像 八股文考试



3、友元函数

当一个函数无法写成对象的 成员函数,但又会频繁的访问该对象类的 私有成员

此时可以写成 友元函数

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

友元分为:友元函数和友元类

3.1 特性

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加 friend 关键字

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



4. 友元类

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

  • 友元关系是单向的,不具有交换性。

    类B 是 类A 的友元函数,类B 可以访问 类A 中的私有成员,但是 类A 不能访问 类B 的成员

    (你把她当作女朋友,她可以去你家玩,但是她不把你当作男朋友,你不能去她家玩 doge)

class A {
	friend class B;  // 类B 是 类A 的友元函数,类 B可以访问 类A 中的私有成员,但是 类 A不能访问 类 B的成员
private:
	char _c;
	int _a;
};
class B {
private:
	int _b;
};
  • 友元关系不能传递: 如果B是A的友元,C是B的友元,则不能说明C时A的友元。

  • 友元关系不能继承,在继承位置再给大家详细介绍。



5. 内部类


5.1 概念

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中 的所有成员。但是外部类不是内部类的友元。

5.2 特性

1、 位置不限:内部类可以定义在外部类的public、protected、private (会受访问限定符控制

2、注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。

3、sizeof(外部类) = 外部类,和内部类没有任何关系。

​ 一个类的大小,就是使用内存对齐计算

​ 内部类不归属一个类的大小中

对象 A 有内部类C,对象 B没有,其他一致:最后计算的大小都相等

class A {
	// 内部类
	class C {
	private:
		int _b;
	};
private:
	char _c;
	int _a;
};

class B {
private:
	char _c;
	int _a;
};


int main()
{
	cout << sizeof(A) << '\n';
	cout << sizeof(B) << '\n';
	return 0;
}

内部类 可以访问 外部类的成员,外部类不可以访问内部类的成员,和 前面讲解的友元类一样,(她可以去你家,但是你不能去她家)

4、类和类之间是独立的,内部类仅仅是==受到外部类的类域的限制,但不能说 类B是类A的成员==

如下面的 类B创建对象时,编译器在全局找不到类B,因为类B在 类A 的类域中


正确用法:指定类域A

int main()
{
	A a;
	A::B b;   
	return 0;
}

同时也受访问限定符的限制:当类B变成 私有时,外界也不能访问

class A {
private:
	char _c;
	int _a;

	// 内部类
	class B {
	private:
		int _b;
	};
};


int main()
{
	A a;
	A::B b;    //  报错
	return 0;
}

外界要通过 A 才能访问到 类B

则有一层”归属的意思“

通过这个性质:内部类 可以访问 外部类的成员,外部类不可以访问内部类的成员

我们可以将前面的 【static章节】中的那道 OJ题 修改一下代码:写成内部类的写法

其根本是:我们的 类Sum 本来就是用于解决 Solution类中的问题的,直接写进去,就有点耦合在一起的感觉

class Solution {
    class Sum {
      public:
        Sum() {
            _cnt += _i;
            _i++;
        }
    };

    static int _cnt;
    static int _i;

  public:
    int Sum_Solution(int n) {
        Sum arr[n];
        return _cnt;
    }
};
int Solution::_cnt = 0;
int Solution::_i = 1;

这里的 _cnt 和 _i 直接写成 class Solution 中的成员变量:反正 类Sum 也可以访问

同时,Solution 的 Sum_Solution函数可以直接 return _cnt; ,无需像之前的写法(写一个 get 函数)

同时,Sum 成为 Solution 的专属类,封装起来,别人不容易使用到,减少一点风险



6、匿名对象

匿名对象:即用即销毁
定义在这一行,使用完直接销毁

生命周期只在这一行

// 匿名对象
class A {
public:
	A(int x = 1) {
		cout << "1" << '\n';
	};
	~A() {
		cout << "2" << '\n';
	}
private:
	int _a;
};
int main()
{
	A a1;
	A a2(1);

	// 匿名对象
	A(10);

	A a3;

	return 0;
}

效果演示:调用 构造函数就打印 1,调用 析构函数就打印 2

可以发现 匿名对象 即用即销毁
在这里插入图片描述


int main()
{
    // 非匿名对象需要写两行
	A a;
	a.func();

	// 匿名对象只需要写一行
	A().func();

	return 0;
}

匿名对象 传参的对象也具有常性,接收参数也要 使用 const

class A {
public:
	A(int x = 1) {
		cout << "1" << '\n';
	};
	~A() {
		cout << "2" << '\n';
	}
	
private:
	int _a;
};

void func(const A& a) {
	// .... 
}

int main()
{
	func(A(2)); //  匿名对象
	func(2); // 隐式类型转换

	return 0;
}



7、编译器的优化


7.1 场景一:

自定义类型的强制类型转换 的 语法过程:10 先 转换为 临时对象(调用一次构造函数),将临时对象拷贝给 对象 a(调用一次 拷贝构造函数

const A& a = 10;

编译器认为这样子麻烦,因此这类 连续的构造函数+拷贝构造函数 === 直接优化成一次 构造函数

免去一次拷贝构造函数



7.2 场景二:删除线格式

连续的 拷贝构造+拷贝构造:优化成 拷贝构造

其实那两次的 拷贝构造都是 中间过程,编译器直接省去中间过程中间变量,在 func 函数结束前,用 对象 a 拷贝构造给 对象 ret

即 对象a 先拷贝给 对象ret 再进行析构

A func() {
	A a; // 构造
	return a;   // 这里将 a 拷贝给一个 临时对象 ,然后 return
}

int main()
{
	//  连续的 构造+拷贝构造+拷贝构造
	A ret = func(); // ret 拷贝 传过来的那个临时对象
	return 0;
}


7.3 场景三:

这个场景就不会优化了:这个是 赋值运算符表达式

因为这个本质上不是 两次拷贝这种,看似 ”冗余“ 的过程会被优化

A func() {
	A a; // 构造
	return a;   // 这里将 a 拷贝给一个 临时对象 ,然后 return
}

int main()
{
	A ret;
	ret = func(); // 赋值重载
	return 0;
}

所以,建议不要这样写,而是直接写成 拷贝 A ret = func(); ,好触发编译器的优化



8、再次理解类和对象

现实生活中的实体计算机并不认识,计算机只认识二进制格式的数据。如果想要让计算机认识现实生活中的实体,用户必须通过某种面向对象的语言,对实体进行描述,然后通过编写程序,创建对象后计算机才可以认识。比如想要让计算机认识洗衣机,就需要:

1、用户先要对现实中洗衣机实体进行抽象—即在人为思想层面对洗衣机进行认识,洗衣机有什么属性,有哪些功能,即对洗衣机进行抽象认知的一个过程

2、经过1之后,在人的头脑中已经对洗衣机有了一个清醒的认识,只不过此时计算机还不清楚,想要让计算机识别人想象中的洗衣机,就需要人通过某种面相对象的语言(比如:C++、Java、Python等)将洗衣机用类来进行描述,并输入到计算机中

3、经过2之后,在计算机中就有了一个洗衣机类,但是洗衣机类只是站在计算机的角度对洗衣机对象进行描述的,通过洗衣机类,可以实例化出一个个具体的洗衣机对象,此时计算机才能洗衣机是什么东西。

4、用户就可以借助计算机中洗衣机对象,来模拟现实中的洗衣机实体了。

总结:在类和对象阶段,大家一定要体会到,类是对某一类实体(对象)来进行描述的,描述该对象具有哪些属性,那些方法,描述完成后就形成了一种新的自定义类型才用该自定义类型就可以实例化具体的对象

直接优化成一次 构造函数**

免去一次拷贝构造函数


  • 53
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值