首页 > 其他分享 >类和对象(下)

类和对象(下)

时间:2024-07-24 17:27:09浏览次数:16  
标签:初始化 函数 对象 成员 int 静态 构造函数

目录

构造函数:再次深挖

构造函数函数体赋值

初始化列表

概念

 为什么有初始化列表?

explicit 关键字

static 成员

引入

概念

特性

小结

C++11 成员初始化的新方式

友元

概念

友元函数

友元类

内部类


构造函数:再次深挖

构造函数函数体赋值

”构造函数函数体赋值”指的是在构造函数的函数体内部对类的数据成员进行赋值操作

初始化列表

概念

初始化列表是 C++ 中用于在构造函数中对类成员进行初始化的一种机制。

当创建一个类的对象并调用其构造函数时,可以在构造函数的参数列表之后使用冒号开始 : 逗号分隔,接着列出要初始化的成员及其初始值,这就是初始化列表。(也可以写成一行,以下这种写法可读性更好)

 为什么有初始化列表?

因为有些成员必须在初始化列表初始化

  1. 没有默认构造函数的类成员:如果一个类成员所属的类没有默认构造函数,那么在包含该成员的类的构造函数中,就只能通过初始化列表来对其进行初始化。
  2. 引用成员:引用在声明时必须被初始化,且之后不能再重新绑定到其他对象,所以也要在初始化列表中进行初始化。
  3. const 成员:由于 const 成员的值在其声明后不能被修改,所以必须在初始化列表中进行初始化。

当我们在main函数中初始化变量的时候,这三种成员必须初始化,和在类中必须初始化一个道理


在类中,这三类成员必须使用初始化列表初始化

引用和const都必须在定义的时候初始化,而对象需要调用构造函数,但是构造函数又需要传参,所以必须在定义的时候初始化

class A
{
public:
    A(int n)  // 不是默认构造函数,如果加个缺省参数就是默认的构造函数(或者不写)
    {
        _n = n;
    }
private:
    int _n;
};

class B
{
public:
    // 可以理解成初始化列表是对象的成员变量定义的地方
    B(int a, int ref, int num)  // 增加一个参数用于初始化引用
        : _aobj(1)  //没有默认构造函数,必须传参过去
        , _ref(ref)  // 初始化引用,ref 是一个已经存在的 int 变量
        , _n(10)   // 因为是const成员,必须声明时候就初始化,之后不能修改
    {}
private:
    // 成员变量的声明
    A _aobj;  // 没有默认构造函数(不用传参就可以调的那个构造函数)
    int& _ref;  // 引用
    const int _n;  // const
};

通常情况下,除了 const 成员、引用成员和没有默认构造函数的类成员这三类必须在初始化列表中进行初始化之外,其他普通成员变量既可以在初始化列表中初始化,也可以在构造函数体内进行赋值。

但一般建议在初始化列表中进行初始化,这样效率更高且代码更清晰。

class A
{
public:
    A(int n)  
    {
        _n = n;
    }
private:
    int _n;
};

class B
{
public:
    B(int a, int ref, int num) 
        : _aobj(1) 
        , _ref(ref) 
        , _n(10)
       // ,_x(num)  在初始化列表初始化
    {
        _x = num;  // 在函数体内赋值
    }
private:
    A _aobj;  
    int& _ref;  
    const int _n; 

    //其它成员变量
    int _x;
};

尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先去使用初始化列表初始化(在有默认构造函数的情况下,如果没有默认构造函数,必须在初始化列表初始化)

class Time 
{
public:
	Time(int n = 0) 
	{
		_n = n;
	}
private:
	int _n;
};
class Date 
{
public:
	Date(int day)
	{
		Time t(1);
		_t = t;
	}
private:
	int _day;
	Time _t;
};

尽管在 Date 类的构造函数中,在函数体内对 _t 进行了赋值,但实际上,对于自定义类型成员变量 _t,编译器会先尝试使用初始化列表来进行初始化。

 

由于没有在初始化列表中对 _t 进行初始化,编译器会先调用 Time 类的默认构造函数(因为在 Time 类中提供了一个带默认值的构造函数)来初始化 _t,然后再执行构造函数体内的赋值操作。

                                                                                                      

如果没有默认的构造函数,必须使用初始化列表初始化

一般来说,为了提高效率和代码的清晰性,对于自定义类型成员变量,建议在初始化列表中进行初始化。


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

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();
	return 0;
}

 初始化的顺序和声明的顺序一样,先初始化_a2,  _a2为随机值,在初始化_a1为1,通常情况下,我们都是把声明和定义一一对应起来初始化列表


explicit 关键字

在 C++ 中,explicit 关键字主要用于修饰单参数的构造函数,其作用是禁止隐式类型转换。

C++11中,可以修饰多参数的构造函数,但是这种多参数的隐式类型转换相对较少,并且需要满足特定的条件

int main()
{
	Date d1(1); // 构造
	Date d2 = 2; //隐式类型转换,构造出 tmp(2),再用tmp拷贝构造d2(tmp) = 优化成直接构造
	Date d3 = d1; //拷贝构造

    
    //和上面也是同理
	int i = 1;
	double d = i; //会先进行隐式类型转换,生成一个临时的 double 值,然后将这个临时值赋值给 d
    //如果使用引用,也会产生临时变量,而临时变量具有常性,所以需要加上const
    //double& d  = i;
    const double& d = i; 
}

不想让这种隐式类型转换发生就加上这个关键字 explicit 


C++11中,允许有多个参数赋值

static 成员

引入

我们都知道类只有在构造和拷贝构造的时候会产生对象,但是以下的缺陷是,谁都可以对n进行修改,这样计算可以,但是失去了封装性的意义

概念

在 C++ 中,static 成员是指在类中声明为 static 的成员变量或成员函数,称为静态成员变量或静态成员函数。

static 成员变量:

1,不属于类的任何对象,而是被类的所有对象共享。

2,即使没有创建类的对象,也可以通过类名直接访问和操作。

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

静态成员函数:

也满足前面提到的前两点:

但是静态成员函数不需要在类外进行初始化,因为函数的定义本身就可以在类内完成。

静态函数没有this指针,属于整个类,而非静态函数属于某个对象,因为含有this指针,需要传参(传对象过去) ,隐含的this指针接收,通过this指针可以访问私有成员,而static不能访问私有成员,也不能调用类里面的其它函数,因为没有this指针

非静态的函数有this指针,所以可以调用类中的任何的函数,包括静态函数

class A {
public:
    void f1() {  // 非静态成员函数,可以通过 this 指针访问私有成员
        this->_n = 10;
    }

    static void f2() {  // 静态成员函数,没有this指针,无法访问私有成员
        // 错误:无法通过 this 指针访问 _n
        // this->_n = 20; 
    }
private:
    int _n;
};

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

class A 
{
public:
	A() 
	{
		++_count;
	}
	A(const A& t) 
	{
		++_count;
	}
    static int GetCount() 
	{
		return _count;
	}
private:
	static int _count;
};
A f1(A a) 
{
	return a;
}
//类外定义静态成员变量
int A::_count = 0;
int main()
{
	A a1;
	A a2;
	f1(a1);
	f1(a2);
	cout << A::GetCount() << endl; // 6
    // 如果类中的Getcount函数不加static就需要用具体的对象调用该函数
    //cout << a2.GetCount() << endl;
	return 0;
}

特性

1.静态成员为所有类对象所共享,不属于某个具体的实例
2.静态成员变量必须在类外定义,定义时不添加static关键字
3.类静态成员即可用类名::静态成员或者对象.静态成员来访问
4.静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5.静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值

问题:

静态成员函数可以调用非静态成员函数吗

非静态成员函数可以调用类的静态成员函数吗

调用静态成员函数的方式

小结

  1. static成员变量不存在对象中,存在静态区,属于这个类的所有对象,也是属于这个类。
  2. static成员函数,没有this指针,不使用对象就可以调用。 类名 : : func();
  3. static成员函数中,不能访问非静态的成员(成员变量+成员函数)
  4. 访问限定符对 static 成员同样生效,例如如果 static 成员被声明为 private ,则在类外不能直接访问。

C++11 成员初始化的新方式

非静态成员可以在声明时给缺省值,静态成员不行

以下不是定义,而是在声明的时候给缺省值

class Date 
{
public:
	Date() 
		:_year(10)   //不使用缺省值0
	{}
	void Print() 
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	//C++11 声明时给缺省值
	int _year = 0;  //如果初始化了就用初始化的值,没有初始化就用缺省值
	int _month = 1;
	int _day = 1;
};
int main() 
{
	Date d1;
	d1.Print(); // 10-1-1
	return 0;
}

友元

概念

友元分为:友元函数和友元类,友元提供了一种突破封装的方式,被指定为友元的函数或类可以访问该类的私有成员和保护成员,就好像它们是公共成员一样。有时提供了便利,但是友元会增加耦合度,破坏了封装,所以友元不宜多用

友元函数

  1. 友元函数是在类外面访问类的私有成员或保护成员的一种方式。
  2. 但需要注意的是,虽然友元函数提供了这种访问权限,但过度使用友元函数可能会削弱类的封装性和信息隐藏原则。
  3. 比如,如果多个函数都被设为友元,可能会导致类的内部实现细节过度暴露,使得后续对类的修改和维护变得复杂。所以,在设计类时,应尽量通过提供公有成员函数来控制对私有和保护成员的访问,只有在极少数必要的情况下才使用友元函数。
  4. 友元函数的意思就是,类外面的函数想访问类内部的私有或者公有成员,就把外面的函数变成友元函数


在类外面想访问类里面的成员,可以借助友元,但是感觉这个东西不太合理,我还不如直接把函数写到类里面(成员函数),有些地方必须用友元

1,在了解某种场景必须使用友元之前,我们先知道输入和输出的类型

2,当自定义类型想想内置类型一样使用  <<   输出流运算符的时候,必须重载

3,重载该运算符

cout 传给this,d1传给了out,这传的反了,所以写成 d1 << cout,我选择用运算符重载就是为了提高可读性
我必须写成cout << d1;这种情况下cout在前,传给隐含this就是cout

把这个重载函数写到日期类里面,但是日期类对象抢了第一个隐含的位置,又想访问该成员函数,日期类又抢了第一个位置,这种情况下如何解决

 

这种情况下我们必须使用友元函数解决

class Date 
{
public:
	void Print() 
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//运算符的重载,友元函数的声明
	friend void operator<<(ostream& out, const Date& d);
private:
	int _year = 0;
	int _month = 1;
	int _day = 1;
};
//友元函数
void operator<<(ostream& out, const Date& d) 
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
}
int main()
{
	Date d1;
	cout << d1;
	// cout.operator(&cout,d1);
	return 0;
}

代码优化:有时候我们需要连续输出

class Date 
{
public:
	Date(int year = 0, int month = 1, int day = 1) 
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() 
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//运算符的重载,友元函数的声明
	friend ostream& operator<<(ostream& out, const Date& d);
private:
	int _year = 0;
	int _month = 1;
	int _day = 1;
};
//友元函数
ostream& operator<<(ostream& out, const Date& d) 
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
	return out;  
}
int main()
{
	Date d1(2024, 7, 24);
	Date d2(2024, 7, 25);
	//有时候我们会连续输出
	cout << d1 << d2 << endl;
	return 0;
}

4,输入运算符 >> 的重载 ,和输出运算符重载同理

class Date 
{
public:
	Date(int year = 0, int month = 1, int day = 1) 
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() 
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//运算符的重载,友元函数的声明
	friend istream& operator>>(istream& in, Date& d);
private:
	int _year = 0;
	int _month = 1;
	int _day = 1;
};

//输入需要改变 d 对象不加const
// Date& d 如果不加 引用,d1 传给 d,d1拷贝构造了d,里面的改变不会影响外面d1
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
int main()
{
	Date d1(2024, 7, 24);
	cin >> d1;
	d1.Print(); 
	return 0;
}

5,为什么能自动识别类型,因为存在函数重载,自动匹配类型 ?

这是因为 C++ 的输入输出流(iostream)库对不同的数据类型进行了重载。

重载的 operator<< 函数针对不同的类型有不同的实现,能够根据传入的参数类型自动选择正确的处理方式来进行输出。输入流也是这样的。

 

友元类

如果一个类 A 被另一个类 B 声明为友元类,那么类 B 的所有成员函数都可以访问类 A 的私有成员和保护成员。

由于 ClassB 被声明为 ClassA 的友元类,所以 ClassB 的成员函数 accessClassA 能够直接访问 ClassA 的私有成员 privateMember

 

友元类的使用需要谨慎,因为它在一定程度上破坏了类的封装性。

class ClassA 
{
private:
    int privateMember;

public:
    friend class ClassB;  // 将 ClassB 声明为友元类
};

class ClassB 
{
public:
    void accessClassA(ClassA obj) 
    {
        cout << obj.privateMember << endl;  // 可以访问 ClassA 的私有成员
    }
};

内部类

内部类(也称为嵌套类)是在另一个类的内部定义的类。

 

内部类的主要特点包括:

 
  1. 内部类的作用域被限制在其外部类的范围内。
  2. 内部类可以访问外部类的私有成员和保护成员。
//内部类
class A 
{
private: 
	static int k;
	int h;
public:
	class B //B天生就是A的友元
	{
	public:
		void foo(const A& a) 
		{
			cout << k << endl;  // 静态成员变量,B是A的友元可以访问私有
			cout << a.h << endl;
		}
	};
};
int A::k = 1;
int main()
{
	A::B b;  //嵌套的类必须指定在哪个个类下
	b.foo(A());
	//b.foo(A()); 的意思是创建一个临时的 A 类对象,
	// 并将其作为参数传递给 b 对象的 foo 函数。
    //具体来说,A() 会创建一个匿名的、临时的 A 类对象,
	// 然后通过 b.foo(...) 调用 B 类中 foo 函数,
	// 并将这个临时创建的 A 对象作为参数传递给 foo 函数进行处理。
}

标签:初始化,函数,对象,成员,int,静态,构造函数
From: https://blog.csdn.net/m0_63207201/article/details/140651910

相关文章

  • Java中string对象是如何实现的?string对象的优化过程
    1.基本实现Java中的String类是一个final类,这意味着它不能被继承。它内部使用一个字符数组(char[])来存储实际的字符序列。这个字符数组是私有的,并且不能被外部直接访问或修改(除了通过String类提供的公共方法)。String类还包含一些字段来跟踪字符串的长度(value.length)和哈希......
  • Java学习笔记(七)面向对象编程(中级部分)
    Hii,mJinXiang⭐前言⭐本篇文章主要介绍Java面向对象编程(中级部分)包、访问修饰符、封装、继承、super关键字、多态、向上(下)转型、equals、hashCode、断点调试等知识的详细使用以及部分理论知识......
  • Python 类型暗示​​一个充满 myclass 对象的双端队列
    使用Python3.6或更高版本,我想输入提示一个返回MyClass对象的函数myfunc我如何提示myqueue是一个deque|||充满MyClass对象?objects?fromcollectionsimportdequeglobal_queue=deque()classMyClass:passdefmyfunc(m......
  • 初学Python时需要认识清楚的几个概念:对象、函数、圆括号给、点取、方括号取
    这是我在自学Python的过程中自己挑选和提炼出来的几个重要的概念,之所以特意介绍这些概念,其中包含了我自己的思维方式和我对Python设计理念的认识,有其独特性和局限性。我希望这篇文章能够给喜爱Python的朋友们带来一些启发。1、对象(Object)对象是Python编程的基本单元。就像音是......
  • 模块2 面向对象编程初级 --- 第六章:创建对象
    第六章创建对象主要知识点:1、类的实例化2、构造方法3、对象的使用4、对象的清除学习目标:根据定义的类进行实例化,并且运用对象编写代码完成一定的功能。本章对类进行实例化,生成类的对象,利用对象开始软件的设计过程,掌握对象的使用方法。6.1创建对象概......
  • 如何优雅地将复杂的Python对象和SQLAlchemy对象模型类结合起来?
    我有一个相当复杂的类,具有从提供的df到init计算的复杂属性,这些属性可能是最终可以序列化为字符串的其他类类型。在Python中,我想处理对象而不是原始类型,但也想使用SQLAlchemy与数据库交互。表中的列与许多类属性相同,如何优雅地组合这两个类?我可以使用组合并将数据......
  • SQLAlchemy AttributeError:“表”对象在以前运行的模型中没有属性“id”
    我有一个烧瓶应用程序工厂应用程序,其中包含大量模型和视图。一切都工作正常,直到我将某些模块更改为backref以跟上时代的步伐。突然,应用程序无法工作,因为我在不同的模块中收到sqlalchemy属性错误,甚至在用户模块中,而这些模块根本没有被触及。(见下文。)back_populat......
  • 【7种面向对象设计原则】
    一、面向对象设计原则1.1、概述如何同时提高一个软件系统的可维护性和可复用性是面向对象设计需要解决的核心问题之一。在面向对象设计中,可维护性的复用是以设计原则为基础的。每一个原则都蕴含一些面向对象设计得思想,可以从不同的角度提升一个软件系统结构的设计水平。最常......
  • C++核心编程-4、类和对象4—多态
    4.7多态4.7.1多态的基本语法 示例代码如下:#include<iostream>usingnamespacestd;//多态的基本概念//满足动态多态的条件:1、有继承的关系2、子类要重写父类的虚函数//重写:函数返回值类型函数名参数列表完全相同//动态多态的使用://父类的指针或者引用执行......
  • 从零开始学Java(超详细韩顺平老师笔记梳理)08——面向对象编程中级(上)IDEA常用快捷键、包
    文章目录前言一、IDEA使用常用快捷键模板/自定义模板二、包package1.基本介绍2.包的命名规范3.常用的包和如何引入4.注意事项和细节三、访问修饰符(四类)四、封装Encapsulation(重点)1.封装介绍2.封装步骤3.快速入门4.封装与构造器五、继承(重点)1.为什么需要继承2......