详解C++类和对象(下篇)

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

目录

一,再谈构造函数

 1.1 构造函数体赋值

 1. 2 初始化列表 

1.21 自定义类型成员 

1.22  const 成员变量

1.23 引用成员变量  

1. 24 初始化列表的“坑”

1. 3 explicit 关键字 

二,static 成员

2.1 概念 

2.2 特性 

三, 友元

3. 1 友元函数

3. 2 友元类 

特点: 

3. 3 内部类(了解)

3. 31 概念

四, 匿名对象(了解)

五, 编译器对拷贝对象的一些优化

1. 传值传参

2. 传值返回

3. 隐式类型

4.  一个表达式中,连续构造+拷贝构造->优化为一个构造

5. 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造

6. 一个表达式中,连续拷贝构造+赋值重载->无法优化

结语


一,再谈构造函数

 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;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值 ,而不能称作初始化。因为 初始化只能初始 化一次,而构造函数体内可以多次赋值

下面是过往的成员初始化,在该情景下则不太合适:

#include<iostream>
using namespace std;
class Time
{
public:
	Time(int hour = 10)   // 对于在函数体中初始化,必须写缺省参数,否则编译报错。
	{
		_hour = hour;
	}
private:
	int _hour;
};

class Date
{
public:
	Date(int year, int hour)    
	{
		_year = year;
		Time t(hour);
		_t = t;   
	}
private:
	int _year;
	Time _t; 
};

int main()
{
	Date a(2023,2);
	return 0;
}

 详解C++类和对象(下篇)

 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;
};
1.  每个成员变量在初始化列表中 只能出现一次。(初始化只能初始化一次)
( 注意:如果只是 内置类型初始化,那么在 函数体内初始化初始化列表初始化,两者相差不大。)
2.  类中包含以下成员,必须放在初始化列表位置进行初始化:
  • 引用成员变量
  • const成员变量
  • 自定义类型成员(且该类没有默认构造函数时)

1.21 自定义类型成员 

 用初始化列表来完成,自定义类型初始化,看下面代码:

#include<iostream>
using namespace std;
class Time
{
public:
	Time(int hour = 11)   // 区别于函数内构造(见1.1代码,这里必须加),全缺省参数可加可不加。
	{
		_hour = hour;
	}
private:
	int _hour;
};

class Date
{
public:
	Date(int year, int hour)  
		:_t(hour)
	{
		_year = year;
	}
private:
	int _year = 100; // 缺省值,c++11打了个补丁,在内置类型初始化没有给值值时,给缺省值。
	Time _t;1 
};

int main()
{
	Date a(2023,2);
	return 0;
}

所以结合的函数体内部初始化与列表初始化我们可以看出:

1. 如果是函数内部初始化,赋值前还是得初始化,然后赋值,中间需要几次初始化,比较繁琐。

2. 如果在初始化列表初始化,可以不用有全缺省的默认构造函数,直接显示初始化。

结论:

  • 自定义类型成员推荐用列表初始化,没有全缺省参数的构造函数必须用列表初始化。
  • 内置类型推荐写到初始化列表,也可以写到函数体内部,两者随意。(除非为了代码好看,就需要写成函数内部初始化)

1.22  const 成员变量

       原因是 如:const   int   x 必须是在定义的地方初始化。(我是这么理解的,这跟const xx类型的权限性质有关)

class Date
{
public:
	Date(int year, const int x)
		: _z(x)
	{
		_year = year;
	}
private:
	int _year;
	const int _z;
};

int main()
{
	int x = 0;  // 用const修饰,就是想等传参;不修,则是缩小权限传参。
	Date a(2023, x);
	return 0;
}

1.23 引用成员变量  

        引用是另一个变量的“别名”,性质是不允许修改,所以必须在定义的时候初始化。 

class Date
{
public:
	Date(int year, int& x)
		: _z(x)
	{
		_year = year;
	}
private:
	int _year;
	int& _z;
};

int main()
{
	int x = 0;
	Date a(2023, x);
	return 0;
}

1. 24 初始化列表的“坑”

看下面代码会有什么结果:

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  随机值*/

 结果是 D:

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

1. 3 explicit 关键字 

 首先我们查看一下以下代码:


#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year)
	{
		_year = year;
	}
private:
	int _year;
};

void test()
{
	Date x(10);   // 直接调用构造函数
	Date a = 100; // (隐式转化int->Date类型)构造一个Date类型的临时变量 + 
	              //拷贝构造 + 优化(a的拷贝构造无法查看) —> 直接调用构造函数
}

注意:关于编译器对拷贝的优化,本小节后面会讲) 

而这次的 explicit关键字用来修饰构造函数,功能是:禁止类型转化。

 explicit  Date(int year)
    {
        _year = year;
    }

所以Date a = 100,报错

二,static 成员

2.1 概念 

声明为 static的类成员称为 类的静态成员,用 static修饰的 成员变量,称之为 静态成员变量;用 static修饰成员函数,称之为 静态成员函数静态成员变量一定要在类外进行初始化
使用场景:static修饰全局变量,在类外可以被任意访问,那如何设计出一个 只能让类访问的全局变量

 比如: 面试题:实现一个类,计算程序中创建出了多少个类对象。

class A
{
public:
	A(int i)
		:_i(i)
	{
		_scout++;
	}

	static int Getscout()   // 类外无法取得static 成员, 所以需要一个类成员函数取得。
	{
		return _scout;      // 在静态区中寻找_scout
	}

	A(A& k)
	{
		_i = k._i;
		_scout++;
	}
private:
	static int _scout; // 声明。 (注意:缺省值为初始化列表提供的,而static成员是在类外定义)
	int _i = 0;
};
int A::_scout = 0;     // 初始化, A::通过类域定义----目的是突破类的限制

int main()
{
	A a(1);
	A b(2);
	A c(3);
	// 静态成员公有
	//cout << c._scout << endl;
	//cout << A::_scout << endl;     // 两种方式并非去访问类,而是为了突破类域,去静态区去寻找
	// 静态成员私有
	cout << c.Getscout() << endl;  // 这里访问成员函数,然后在静态区中寻找静态成员。
	cout << A::Getscout() << endl; // 通过类域访问static成员
}

2.2 特性 

1. 静态成员所有类对象所共享,不属于某个具体的对象,存放在静态区。
2. 静态成员变量必须在 类外定义,定义时不添加static关键字,类中只是 声明
3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问。
4. 静态成员函数 没有隐藏的 this指针,不能访问任何非静态成员,可以访问静态成员变量。(静态成员函数没有this指针,就找不到具体的对象,因此只能访问静态成员)
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制(必要时需要用 成员函数访问私有的静态成员变量)。

三, 友元

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

3.1 友元函数

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

特点:

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

运用如下:

class A
{
public:
	friend int func(const A& a);  // 友元声明
private:
	int _i;
};

int  func(const A& a)   // 普通函数
{
	return a._i;
}

3. 2 友元类 

特点: 

  • 1. 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
  • 2. 友元关系不能传递。(如果C是B的友元, B是A的友元,则不能说明C时A的友元。)
  • 3. 友元关系不能继承,在继承时再给大家详细介绍
  • 4. 友元关系是单向的,不具有交换性。 (比如下面Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接

访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。)

下面就是第4点的实践:

class Time
{
   friend class Date;   // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
 Time(int hour = 0, 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 = 1900, 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;    // 在Date中实例一个Time类对象,过去是无法在Time类外直接访问其私有成员。
};

3. 3 内部类(了解)

Java中用的比较多,而C++用的比较少,这里仅作了解。

3. 31 概念

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类成员。外部类对内部类 没有任何优越的访问权限。(外部与内部有这平等关系)
(注意: 内部类是外部类的友元类,外部类不是内部类的友元类。参见友元类的定义, 内部类可以通过外部类的对象参数来 访问外部类中的所有成员。但是外部类不是内部类的友元。)

详解C++类和对象(下篇)

通过一段代码来测试其内部,外部类关系:

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

	class Time
	{
	public:
		Time(int hour = 0, int minute = 0, int secoud = 0)
			:_hour(hour)
			, _minute(minute)
			, _secoud(secoud)
		{}

		void func(Date& _d) // 内部内类,Date天生是Time的友元类,所以可以通过对象直接访问Date其内部私有成员。
		{
			_d._year = _hour;
			_d._month = _minute;
			_d._day = _secoud;
			cout << _d._year << endl;
			cout << _d._month << endl;
			cout << _d._day << endl;
		}
	private:
		int _hour;
		int _minute;
		int _secoud;

	};

    void fundate()
	{
		cout << _t._hour;  // 向内部类直接访问私有失败
	}

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

int main()
{
	Date::Time b;
	Date z;
	b.func(z);
	return 0;
}
特性:
1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
class A
{
public:

	class B 
	{
	public:
		void func()
		{
			cout << z << endl;  // 访问外部类中的静态变量
		}
	private:
		int _b = 100;
	};

private:
	int _i = 10;
	static int z;
};

int A::z = 10;
int main()
{
	A::B a;
	a.func();
}

详解C++类和对象(下篇)

3. sizeof( 外部类 )= 外部类,和内部类没有任何关系, 下面是验证代码
class A
{
public:
	class B
	{
	public:
	private:
		int _b;
	};
private:
	int _i;
};

int main()
{
	cout << sizeof(A); //运行可知为4字节
}

四, 匿名对象(了解)

 比如,让我们看下面代码:

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

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
class Solution {
public:
	int Sum_Solution(int n) {
		//...
		return n;
	}
};
int main()
{
	A aa1;
	// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
	//A aa1();
	// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
	// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
	A();
	A aa2(2);
	// 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
	Solution().Sum_Solution(10);
	return 0;
}

五, 编译器对拷贝对象的一些优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的。
下面是 VS在debug版本下的测试代码:
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "构造" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "拷贝构造" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "赋值构造" << endl;
		if (this != &aa)
		{
			_a = aa._a;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

void f1(A aa)
{}

A f2()
{
	A aa;
	return aa;
}
int main()
{
	//1. 传值传参
	A aa1;
	f1(aa1);
	cout << endl;
	//2.  传值返回
	f2();
	cout << endl;
	//3. 隐式类型,连续构造+拷贝构造->优化为直接构造
	f1(1);
	//4. 一个表达式中,连续构造+拷贝构造->优化为一个构造
	f1(A(2));
	cout << endl;
	//5. 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
	A aa2 = f2();
	cout << endl;
	//6. 一个表达式中,连续拷贝构造+赋值重载->无法优化
	aa1 = f2();
	cout << endl;
	return 0;
}

 1. 传值传参

详解C++类和对象(下篇)

结果: 

详解C++类和对象(下篇)

2. 传值返回

class A
{
public:
	A()
	{
		cout << "构造" << endl;
	}

	A(const A& z)
	{
		cout << "拷贝构造" << endl;
	}

	~A()
	{
		cout << "析构" << endl;
	}
private:
	int _a = 1;
};

A func()
{
	A a;        // 1次构造
	return a;   // 1次拷贝构造
}

int main()
{
	A k = func(); // 1次拷贝构造,但是编译器会优化,把2次拷贝构造优化为1次
	return 0;

详解C++类和对象(下篇) 可见,1次构造,1次拷贝;但为啥会这样,我们以下面的图来解释:

详解C++类和对象(下篇)

 3. 隐式类型

详解C++类和对象(下篇)

结果:

 详解C++类和对象(下篇)

4.  一个表达式中,连续构造+拷贝构造->优化为一个构造

 详解C++类和对象(下篇)

 结果:详解C++类和对象(下篇)

5. 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造

 详解C++类和对象(下篇)

 结果:详解C++类和对象(下篇)

6. 一个表达式中,连续拷贝构造+赋值重载->无法优化

 详解C++类和对象(下篇)

 总结: 代码优化是编译器的功能,在release版本下代码优化比debug版本优化更强,同时不同的编译器优化的程度也不同。

结语

本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论;如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力文章来源地址https://www.toymoban.com/news/detail-445054.html

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

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

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

相关文章

  • C++ 类和对象(二)构造函数、析构函数、拷贝构造函数

            本文将介绍类的6个默认成员函数中的构造函数、析构函数和拷贝构造函数,赋值重载和取地址重载涉及运算符重载的知识,将在下篇讲解。所谓默认成员函数,也就是每个类都有的成员函数,我们可以显式定义这些函数,否则,编译器会自动生成它们。 目录 前言

    2024年02月09日
    浏览(46)
  • C++ 类和对象(中)构造函数 和 析构函数

     上篇链接:C++ 类和对象(上)_chihiro1122的博客-CSDN博客  我们在C当中,在写一些函数的时候,比如在栈的例子: 如上述例子,用C++ 返回这个栈是否为空,直接返回的话,这栈空间没有被释放,就会内存泄漏,如果我们直接释放这个栈,那么我们就拿不到这个栈是否为空的

    2024年02月02日
    浏览(49)
  • 【c++】类和对象(三)构造函数和析构函数

    🔥 个人主页 :Quitecoder 🔥 专栏 :c++笔记仓 朋友们大家好,本篇文章我们带来类和对象重要的部分, 构造函数和析构函数 如果一个类中什么成员都没有,简称为空类 任何类在什么都不写时,编译器会自动生成以下6个默认成员函数 (用户没有显式实现,编译器会生成的成

    2024年04月12日
    浏览(49)
  • 【C++】类和对象(中)---构造函数和析构函数

    个人主页:平行线也会相交💪 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【C++之路】💌 本专栏旨在记录C++的学习路线,望对大家有所帮助🙇‍ 希望我们一起努力、成长,共同进步。🍓 我们知道类包含成员变量和成员函数,当一个类中

    2024年02月05日
    浏览(104)
  • 【c++】类和对象(四)深入了解拷贝构造函数

    🔥 个人主页 :Quitecoder 🔥 专栏 :c++笔记仓 朋友们大家好啊,本篇内容带大家深入了解 拷贝构造函数 拷贝构造函数是一种特殊的构造函数,在对象需要以同一类的另一个对象为模板进行初始化时被调用。它的主要用途是初始化一个对象,使其成为另一个对象的副本 我们先

    2024年04月16日
    浏览(69)
  • 【C++入门到精通】C++入门 —— 类和对象(构造函数、析构函数)

    目录 一、类的6个默认成员函数  二、构造函数 ⭕构造函数概念 ⭕构造函数的特点 ⭕常见构造函数的几种类型 三、析构函数      ⭕析构函数概念     ⭕析构函数的特点 ⭕常见析构函数的几种类型 四、温馨提示          这一篇文章是上一篇的续集(这里有上篇链接)

    2024年02月15日
    浏览(53)
  • 【C++】类和对象(中)之构造函数与析构函数

    👀 樊梓慕: 个人主页  🎥 个人专栏: 《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》 🌝 每一个不曾起舞的日子,都是对生命的辜负 目录 前言 1.构造函数 1.1概念 1.2特性 2.析构函数 2.1概念 2.2特性 我们继续学习类和对象,今天主要讲解构造

    2024年02月07日
    浏览(49)
  • 【C++初阶】四、类和对象(构造函数、析构函数、拷贝构造函数、赋值运算符重载函数)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【C++初阶】三、类和对象 (面向过程、class类、类的访问限定符和封装、类的实例化、类对象模

    2024年02月05日
    浏览(60)
  • 【C++】类和对象②(类的默认成员函数:构造函数 | 析构函数)

    🔥 个人主页: Forcible Bug Maker 🔥 专栏: C++ 目录 前言 类的6个默认成员函数 构造函数 概念 构造函数的特性及用法 析构函数 概念 析构函数的特性及用法 结语 本篇主要内容:类的6个默认成员函数中的 构造函数 和 析构函数 进入到类和对象内容的第二节,上篇博客中介绍了

    2024年04月16日
    浏览(57)
  • 【C++那些事儿】深入理解C++类与对象:从概念到实践(下)| 再谈构造函数(初始化列表)| explicit关键字 | static成员 | 友元

    📷 江池俊:个人主页 🔥 个人专栏:✅C++那些事儿 ✅Linux技术宝典 🌅 此去关山万里,定不负云起之望 1.1 构造函数体赋值 在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。 虽然上述构造函数调用之后,对象中已经有了一个初始值,但是

    2024年03月21日
    浏览(52)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包