C++好难(4):类和对象(下)

这篇具有很好参考价值的文章主要介绍了C++好难(4):类和对象(下)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

okk我们终于来到了C++类和对象的最后一节,大多都是对之前学习的内容做的补充

所以加油继续冲啦!

       ∧_∧::
   (´・ω・`)::
  /⌒  ⌒)::
 /へ__  / /::
(_\\  ミ)/::
  | `-イ::
  /  y  )::
 //  /::
/ /::
( く:::
|\ ヽ:::


【本节目标】

  1. 再谈构造函数
  2. Static成员
  3. 友元
  4. 内部类
  5. 匿名对象

目录

【本节目标】

1.再谈构造函数

1.1构造函数体赋值

1.2 初始化列表

初始化列表的特性

2. static 成员

2.1概念

2.2特性

3.友元

3.1友元函数

友元函数的特点:

3.2 友元类

4.内部类

4.1 概念

5.匿名对象


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;
};

这里需要注意:

上述代码再构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对象成员变量的初始化,

构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

理由如下代码:

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year; // 第一次赋值
		_year = 2022; // 第二次赋值
		//...还可以赋值很多次

		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

这段代码中,构造函数对_year的多次复制,其编译过程是被允许的。

那么既然构造函数里面进行的是赋值的话,什么时候才是成员变量的初始化?

这就要引出我们真正的初始方式:初始化列表

1.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;
};

int main()
{
	Date d1;// 类对象的整体初始化
//(初始化了类对象,但没有初始化成员变量),成员变量放到了初始化列表里面解决

	return 0;
}

初始化列表是对成员函数的初始化,他直接跟在析构函数参数括号的后面

初始化列表的特性

特性一:
解决一些必须在声明时就要初始化的成员变量

(1)const修饰的成员变量

class Date
{
private:
	const int _year; // const修饰的成员变量只能在定义时初始化
};

const 修饰的变量也必须在定义时就给其一个初始值,也必须使用初始化列表进行初始化。

(2)引用成员变量

class Date
{
private:
	int& _year; // 引用成员变量只能在定义时初始化
};

引用类型的变量在定义时就必须给其一个初始值,所以引用成员变量必须使用初始化列表对其进行初始化。

(3)自定义类型成员(该类没有默认构造函数)

class A 
{
public:
	A(int val) //注:这个不叫默认构造函数(因为需要传参调用)
	{
		_val = val;
	}
private:
	int _val;
};

class B
{
public:
	B()
		:_a(2023) //所以必须使用初始化列表对其进行初始化
	{}
private:
	A _a; //自定义类型成员(该类没有默认构造函数)⬆
};

在初始化列表:_a(2023)也就是对A类的构造函数进行的一次调用

肯定有人会有这样的疑惑:   为什么不直接在类中声明时就给其初始化?

像这样:,但这样的写法,放在现在是可以的,但在C11发布之前是不行的

class A
{
public:
	A()
	{}

private:
	int a;
	const int a1 = 1;
	int& b = a;
};

int main()
{
	A d1;

	return 0;
}

要注意:

在类里面声明时给值,不叫做初始化,而是缺省参数,在C11发布之前不允许在类里面直接定义,所以才会有初始化列表的应用

特性二:
每个成员变量在初始化列表中,只能出现一次,(只能初始化一次)

因为初始化只能进行一次,所以同一个成员变量在初始化列表中不能多次出现

特性三:
在你不写初始化列表时,系统自动生成的初始化列表,对内置类型不处理,对自定义类型也是去调用它的初始化类别,

和构造函数、析构函数的特点很像

因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

所以,在我们写构造函数的时候,尽量使用初始化列表进行初始化,能提高效率

如下:

// Date类
class Date
{
public:
	Date(int day = 0)
	{
		_day = day;
	}
private:
	int _day;
};

// Test类
class Test
{
public:
	Test(int day)
		:_d(12)//调用Date的构造函数
	{}
	//这样用初始化列表,只用调用一次Date类的构造函数


	Test(int day)
	{ 
		Date t(day);
		_d = t;
	}
	//这样写的话,会调用两次Date类的构造函数
	//因为系统在自动生成初始化列表时就会调用一次,之后在{ }里面又会调用一次

private:
	Date _d;
};

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

int i = 0;

class Test
{
public:
	Test()
		:_b(i++)
		, _a(i++)
	{}
	void Print()
	{
		cout << "_a:" << _a << endl;
		cout << "_b:" << _b << endl;
	}
private:
	int _a;
	int _b;
};

int main()
{
	Test test;
	test.Print();
	return 0;
}

结果:

C++好难(4):类和对象(下)

可以看到代码中,在初始化列表里面,我们先初始化的_b,然后是_a,但是结果却不按这个顺序走,因为初始化顺序是按照声明时的顺序走的

2. static 成员

2.1概念

声明为static的类成员称为类的静态成员

static修饰的成员变量,称之为静态成员变量
static修饰的成员函数,称之为静态成员函数

静态成员变量一定要在类外进行初始化

2.2特性

特性一

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

先看下面的代码

C++好难(4):类和对象(下)

我们发现 Test2 的类里面比 Test1 类多定义了一个静态变量 _aaa 但是其空间并没有增加

因为静态成员 _aaa 是存储在静态区的,属于整个类,也属于类的所有对象。所以计算类的大小或是类对象的大小时,静态成员并不计入其总大小之和。

特性二

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

class Date
{
private:
    //静态成员变量的声明
	static int _year;
	static int _month;
	static int _day;
};

// 静态成员变量的定义初始化
int Date::_year = 2023;
int Date::_month = 5;
int Date::_day = 8;

特性三

(3)类静态成员即可用 类名::静态成员函数 或者 对象.静态成员函数 来访问

class Date
{
public:
	static void Print()
	{
		cout << _year << "-" << _month << endl;
	}
private:
	static int _year;
	static int _month;
};

int Date::_year = 2022;
int Date::_month = 10;

int main()
{
	Date d1;

	d1.Print(); // 对象.静态成员函数

	Date::Print(); // 类名::静态成员函数
	return 0;
}

应为static定义的成员函数是所有类对象所共享的,所以它既可以用类域::来定位,也可以用对象.来定位

特性四

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

class Date
{
public:
	static void Print()
	{
		cout << _year << endl; //静态的成员可以访问
		cout << _month << endl; //不能访问非静态成员
	}
private:
	static int _year;
	int _month;
};

int Date::_year = 2022;

int main()
{
	Date d1;

	d1.Print(); // 对象.静态成员

	return 0;
}

C++好难(4):类和对象(下)

 含有静态成员变量的类,一般含有一个静态成员函数,用于访问静态成员变量。

特性五

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

C++好难(4):类和对象(下)

在静态成员变量设为private时,经过我们通过类域进行访问,也是不行的

特性六

 (6)访问静态成员变量的方法:

class Date
{
public:
	static int GetMonth()
	{
		return _month;
	}
	static int _year;
private:
	static int _month;
};

//静态成员变量的定义初始化
int Date::_year = 2023;
int Date::_month = 5;


int main()
{
	Date d1;

	//对公共属性的静态成员进行访问
	cout << d1._year << endl; // 1.通过类对象突破类域进行访问
	cout << Date()._year << endl; // 2.通过匿名对象突破类域进行访问
	cout << Date::_year << endl; // 3.通过类名突破类域进行访问

	//对私有属性的静态成员进行访问
	cout << d1.GetMonth() << endl; // 1.通过类对象突破类域进行访问
	cout << Date().GetMonth() << endl; // 2.通过匿名对象突破类域进行访问
	cout << Date::GetMonth() << endl; // 3.通过类名突破类域进行访问

	return 0;
}

3.友元

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

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

3.1友元函数

问题:现在尝试去重载operator<<,然后会发现没办法将operator<<重载成成员函数。

如下展示:

#include <iostream>
using std::cin;
using std::cout;
using std::endl;

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

    std::ostream& operator<<(std::ostream& out)
    {
        out << _year << "-" << _month << "-" << _day;
        return out;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d(2017, 12, 24);
    cout << d;

    return 0;
}

C++好难(4):类和对象(下)

因为 cout 的输出流对象和隐含的this指针抢占第一个参数的位置。
this指针默认是第一个参数也就是左操作数了。
但是实际使用中cout需要是第一个形参对象,才能正常使用。
所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员此时就需要友元来解决。operator>>同理。

用友元来解决如下:

class Date
{
	//友元函数
	// 标准流输出 --> printf
	friend std::ostream& operator<<(std::ostream& out, const Date& d); 
	// 标准流插入 --> scanf
	friend std::istream& operator>>(std::istream& in, Date& d); 
public:
	Date(int year = 2023, int month = 5, int day = 8)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};

// <<运算符重载---输入
ostream& operator<<(std::ostream& out, const Date& d) 
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
	return out;
}

// >>运算符重载---输出
istream& operator>>(std::istream& in, Date& d) 
{
	in >> d._year >> d._month >> d._day;
	return in;
}

int main()
{
	Date d;
	cin >> d;
	cout << d << endl;
	return 0;
}

注意:其中 cout ostream 类的一个全局对象cin istream 类的一个全局变量
<< 和 >> 运算符的重载函数具有返回值是为了实现连续的输入和输出操作。

友元函数的特点:

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

3.2 友元类

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

class Time
{
	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
	Time(int hour = 12, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{}

private:
	int _hour;
	int _minute;
	int _second;
};

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

	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}
private:
	// 内置类型成员
	int _year;
	int _month;
	int _day;

	// 自定义类型成员
	Time _t;
};
  • 友元关系是单向的,不具有交换性。

比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

  • 友元关系不能传递

如果C是B的友元, B是A的友元,则不能说明C时A的友元。

  • 友元关系不能继承

4.内部类

4.1 概念

如果一个类定义在另一个类的内部,这个内部类就叫做内部类

如下:

class A
{
public:
	// 内部类
	class B // B天生就是A的友元,在B里面可以直接访问A的私有成员
	{
	public:
		void Print(const A& a)
		{
			cout << y << endl; // 可以直接访问静态成员
			cout << a.h << endl; // 也可以访问普通成员
		}
	private:
		int _b;
	};

private:
	static int y;
	int h;
};

int A::y = 1;

int main()
{
	A a; // 定义a对象

	A::B bb; // 定义bb对象

	bb.Print(a); // 把a对象传给bb对象,打印
	return 0;
}

上述代码中,我们在A类里面定义了一个B类,B就叫做A的内部类

B天生就是A的友元,B可以访问A的私有,但不能访问B的私有

注意:

  • 1. 内部类可以定义在外部类的public、protected、private都是可以的。
  • 2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  • 3. sizeof(外部类)=外部类,和内部类没有任何关系。

5.匿名对象

对于一个类 class  A{ };

通过前面的学习,我们知道,在定义类对象的时候不能这样写:A  aa1();

但是我们可以写成这样: A();

匿名对象的特点:

(1)匿名对象的声明周期只有写匿名周期的那一行

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a;
};

int main()
{
    A aa1;    

	A();

    return;
}

通过调试我们可以看到,匿名对象在离开他那一行的时候,他的生命周期就结束了

C++好难(4):类和对象(下)

这样写的方式我们就称为匿名对象,那么匿名对象有什么用呢?

看看下面这段代码:

class Solution {
public:
	int Sum_Solution(int n) 
	{
		//...
		return n;
	}
};
int main()
{
	Solution aaa;
	cout << aaa.Sum_Solution(10) << endl;//一般调用

	cout << Solution().Sum_Solution(10) << endl;//匿名对象调用

	return 0;
}

如果我们要用一个类里面的某一个成员函数,我们可能需要真没为其定义一个实例对象 aaa 出来,然后再去调用该类里面的成员函数

比较麻烦,

所以我们可以直接用过匿名对象来进行解决,匿名对象再其调用完成后会直接销毁,不会占用资源文章来源地址https://www.toymoban.com/news/detail-436789.html

到了这里,关于C++好难(4):类和对象(下)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】类和对象-封装

    在main函数前重新补上isSame函数 在Cube类里面添加issamebyclass,利用成员函数判断两个立方体是否相等 自己写的代码: B站视频链接: https://www.bilibili.com/video/BV1et411b73Z/?p=105spm_id_from=333.1007.top_right_bar_window_history.content.clickvd_source=fb8dcae0aee3f1aab700c21099045395

    2024年02月15日
    浏览(47)
  • 【C++】类和对象(一)

    C++对C语言的补充最重要的功能之一就是类和对象的引入,在学习完漫长的C语言,从这里就算是开始踏上了高级语言之路,C++的这门语言有太多细节了,所以也要花费更多的时间和更多的精力,去面对更多的困难,做好心里准备,学校杂七杂八的事情,想要让学习和生活上的

    2024年02月05日
    浏览(27)
  • 【C++】类和对象(下)

    在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。 虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化, 构造函数体中的语句只能将其称为赋初值 ,而不能称作初始化。因为初始化只能

    2024年03月25日
    浏览(48)
  • 【C++】类和对象(3)

    首先我们先回顾一下构造函数,对象的初始化由构造函数来完成,我们可以在构造函数的函数体内对对象的成员变量进行赋值,但这就有一个问题,如下: 答案:显然不是,因为变量只能定义一次。 也就是说,构造函数的函数体内部并不是初始化的地方(定义的地方),而

    2024年02月06日
    浏览(33)
  • 【C++】类和对象(上)

    个人主页:平行线也会相交 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【C++之路】 C语言是面向过程的,而C++是面向对象的,那面向过程和面向对象到底是什么呢? 我们拿一个非常典型的 外卖系统 来进行举例: 面向过程: 我们知道外面

    2024年02月02日
    浏览(44)
  • 【C++】类和对象(中篇)

    在往期 类和对象(上篇) 中,我们初步接触了C++中的类和对象,接下来我会和大家分享有关这个知识点更多的内容~ 如果一个类中什么成员都没有,简称为空类。空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成默认成员函数。 默认成员函数:用户

    2024年02月15日
    浏览(57)
  • 【C++】类和对象(四)

    前言:在类和对象中,我们走过了十分漫长的道路,今天我们将进一步学习类和对象,类和对象这块荆棘地很长,各位一起加油呀。 💖 博主CSDN主页:卫卫卫的个人主页 💞 👉 专栏分类:高质量C++学习 👈 💯代码仓库:卫卫周大胖的学习日记💫 💪关注博主和博主一起学习

    2024年02月19日
    浏览(33)
  • 【C++】:类和对象(1)

    朋友们、伙计们,我们又见面了,本期来给大家解读一下有关C++中类和对象的知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成! C 语 言 专 栏: C语言:从入门到精通 数据结构专栏: 数据结构 个  人  主  页 : stackY、 目录 1.面向过程和面

    2024年02月08日
    浏览(36)
  • 【C++】类和对象(上篇)

    🚩 C语言 是 面向过程 的, 关注 的是 过程 ,分析出求解问题的步骤,通过函数调用逐步解决问题 🚩 C++ 是基于 面向对象 的, 关注 的是 对象 ,将一件事情拆分成不同的对象,靠对象之间的交互完成 🌰 比如: C语言结构体中只能定义变量, 在C++中,结构体内不仅可以定

    2024年01月22日
    浏览(39)
  • c++类和对象(封装)

            C++面向对象的三大特性为: 封装、继承、多态         C++认为万事万物都皆为对象,对象上有其属性和行为 例如:         人可以作为对象,属性有姓名、年龄、身高、体重...,行为有走、跑、跳、吃饭、唱歌...车也可以作为对象,属性有轮胎、方向盘、

    2024年02月08日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包