首页 > 编程语言 >C++之类和对象(3)

C++之类和对象(3)

时间:2024-03-18 12:32:30浏览次数:21  
标签:友元 初始化 函数 对象 成员 C++ int 之类 public

目录

1. 再谈构造函数

1.1 构造函数体赋值

 1.2 初始化列表

1.3 explicit 

 2. static成员

2.1 概念

 3. 友元

3.1 友元函数

3.2 友元类

4. 内部类

 5. 匿名对象

6. 拷贝对象时编译器做出的优化


1. 再谈构造函数

1.1 构造函数体赋值

class Date
{
public:
    Date(int year=2024, int month=3, int day=16)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void pri() {
        cout << _year <<" "<< _month <<" "<< _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main() {
    Date a;
    Date b(111);
    a.pri();
    b.pri();
    return 0;
}

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

 

 1.2 初始化列表

class Date
{
public:
	Date(int year, int month, int day)
        //初始化列表
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	void pri() {
		cout << _year << " " << _month << " " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main() {
	Date a(1,2,3);
	a.pri();
	return 0;
}


初始化列表是每个成员变量定义初始化的位置

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

const成员变量无法在函数体初始化,得在初始化列表初始化

int& ref;引用也得在初始化列表,因为引用定义必须初始化

自定义类型成员(且该类没有默认构造函数时)也在初始化列表



 

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

最后输出 1   -858993460

因为先初始化a2因为a2先在类中声明而且用的是a1的值初始化而a1是随机值

a1最后初始化为a,是1

能用初始化列表就用初始化列表

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

1.3 explicit 

单参数类型: 

class A {
public:
	A(int a=0) 
	:_aa(a)
	{
		cout <<_aa << endl;
	}
	
private:
	int _aa;
};
class B {
private:
	int _a;
	int* p = nullptr;
	int* pp = (int*)malloc(4);
};
int main() {
	B bb;
	A c1(1);
	A c2 = 2;//单参数构造函数支持隐式类型转换
	//所以是2构造一个临时对象然后拷贝构造
	const A& c3 = 4;
	return 0;
}

缺省值可以是其他的变量不局限于常量
如果不想存在隐式类型转换的话可以加explicit在构造函数前面:

c2和c3就出错了

 多参数类型:

class A {
public:
	 A(int a=0,int b=1) 
	:_aa(a)
    ,_bb(b)
	{
		cout <<_aa <<" ";
		cout << _bb << endl;
	}
	
private:
	int _aa;
	int _bb;
};
class B {
private:
	int _a;
	int* p = nullptr;
	int* pp = (int*)malloc(4);
};
int main() {
	B bb;
	A c1(1,2);
	A c2 = {3,4};
	const A& c3 = {5,6};
	return 0;
}
多参数支持花括号产生隐式类型转换

同样加了explicit就不行了:

 用explicit修饰构造函数,将会禁止构造函数的隐式转换

 2. static成员

2.1 概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化

静态成员函数没有this指针所以他只是为了访问静态成员变量他访问不了非静态成员变量因为没this

class A
{
public:
    A() {
        ++n;
    }
    A(const A& aa) {
        ++n;
    }
//private:
    static int n;//声明
    //属于整个类,但本质还是静态全局变量
};
int A::n = 0;//在类外定义
void func() {
    n += 1;//那么这里就访问不了n了
}
int main() {
    A a;
    A b;
    cout << n << endl;//这里也是访问不了n
    return 0;

}

 

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()
	{
		cout << A::GetACount() << endl;
		A a1, a2;
		A a3(a1);
		cout << A::GetACount() << endl;
	}
	int main() {
		TestA();
		return 0;
	}

最终结果0 3

当创建对象 a1 时会调用默认构造函数 A()

输出 _scount 的初始值,初始值为 0。

创建对象 a1,_scount 自增为 1。

创建对象 a2,_scount 再次自增为 2。

通过复制构造函数创建对象 a3,_scount 再次自增为 3。

输出 _scount 的最终值,值为 3。

注意:

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

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

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

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

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

 3. 友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元破坏了封装,所以友元不宜多用-慎用

3.1 友元函数

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

class Date
{
	//友元函数
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
		
public:
	Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
 
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
	return out;
}
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
int main()
{
	Date d1;
	cin >> d1;
	cout << d1;
	return 0;
}

友元函数是普通的全局函数:

#include <iostream>
 
using namespace std;
 
class A
{
 
 private:
     int A;
  public:
     print(){};
     
     //声明全局函数 person 是 类A 的友元函数
     friend void person (int &x);
}
 
 
void person(int &x)
{
   //使用了类A的成员变量age
   cout << "age=" << p.age << endl;
  
}
 
int main ()
{
   A p(22);
   person(p);
   return 0;
}

友元函数是其他类的成员函数:

#include <iostream>
 
using namespace std;
 
class A
{
 
 private:
     int A;
  public:
     print(){};
     
     //声明类B的成员函数 person 是 类A 的友元函数
     friend  void B::person (int &x);
}
 
 
class B
{
  private:
     int B;
  public:
     person(int &x);
 
}
 
B::person(int &x)
{
   //因为类B的成员函数person是类A的友元函数,所以看可以使用类A的成员变量age
   cout << "age=" << p.age << endl;
  
}
 
int main ()
{
   A p(22);
 
   B q;
   q.person(p);
   return 0;
  
}

注意:

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

3.2 友元类

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

友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接
访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
友元关系不能传递:如果C是B的友元, B是A的友元,则不能说明C时A的友元。
友元关系不能继承(后续解析)

//时间类
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;
};

优缺点总结 :

点:友元函数不是类的成员但是却具有成员的权限,可以访问类中受保护的成员,这破坏了类的封装特性和权限管控;

优点:可以实现类之间的数据共享;比如上面互为友元类,则可以互相访问对方受保护的成员;

总结:友元函数是一种破坏封装特性的机制,可以让程序员写代码更灵活,但是不能滥用

4. 内部类

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

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

特性:

  • 1. 内部类可以定义在外部类的public、protected、private都是可以的。
  • 2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  • 3. sizeof(外部类)=外部类,和内部类没有任何关系。
    // 1、B类受A类域和访问限定符的限制,其实他们是两个独立的类
    // 2、内部类默认就是外部类的友元类
    class A
    {//外部类不能访问内部类
    public:
    	class B // B天生就是A的友元
    	{
    	public:
    		void print(const A& a)
    		{
    			cout << k << endl;   //可以直接访问A的静态成员变量
    			cout << a.h << endl; //也可以访问A的成员变量
    		}
    	};
    private:
    	static int k;
    	int h;
    };
    int A::k = 1;
     
    int main()
    {
    	A::B b;
    	b.print(A());
    	cout << sizeof(A) << endl;  //8   外部类的大小与内部类无关  a与b相互独立的只不过b受a类的局域限制
    	return 0;
    }
    类本身不占用空间

C++不太喜欢使用内部类,所以了解即可 

 5. 匿名对象

匿名对象的特点就是不用取名字,生命周期只存在定义的这一行。

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 aa1();// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
	
	// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
	A();// 但是他的生命周期只有这一行,紧接着它的下一步就会自动调用析构函数
 
	A aa2(2);
	return 0;
}

 应用场景: 当我们做C++的OJ题时会发现都是将其封装在一个Solution类中的,假设我们需要调用这个类中的某一个函数,是需要先创建一个Solution的对象,然后通过这个对象进行调用,这样的话有点麻烦,我们可以直接使用匿名对象来调用这个类中的成员函数。

class Solution {
public:
	int Sum_Solution(int n) {
		//...
		return n;
	}
};
int main()
{
	// 1.基本方法
	Solution sl;
	sl.Sum_Solution(10);
 
	// 2.匿名对象
	Solution().Sum_Solution(10);
 
	return 0;
}

6. 拷贝对象时编译器做出的优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << 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()
{
	// 传值传参
	A aa1;
	f1(aa1);
	cout << endl;
 
	// 传值返回
	f2();
	cout << endl;
 
	// 隐式类型,连续构造+拷贝构造->优化为直接构造
	f1(1);
 
	// 一个表达式中,连续构造+拷贝构造->优化为一个构造
	f1(A(2));
	cout << endl;
 
	// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
	A aa2 = f2();
	cout << endl;
 
	// 一个表达式中,连续拷贝构造+赋值重载->无法优化
	aa1 = f2();
	cout << endl;
	return 0;
}


标签:友元,初始化,函数,对象,成员,C++,int,之类,public
From: https://blog.csdn.net/yiqizhuashuimub/article/details/136758889

相关文章

  • QT5.14.2 探秘Qt信号槽奥秘--让对象间通信如虎添翼
    一、前言在当今这个万物互联的时代,对象间通信无疑是编程领域中最为基础也最为重要的问题。作为知名的跨平台开发框架,Qt自然也需要解决这一问题。于是,Qt巧妙地提出了信号与槽(Signals&Slots)这一机制,以观察者模式的思路让对象间通信变得行云流水。那么,Qt信号与槽的本质......
  • C++ pointer
    int*pInt=newint;*pInt=5;cout<<"---------------"<<endl;cout<<"&(*pInt)-->"<<&(*pInt)<<endl;cout<<"pInt-->"<<pInt<<endl......
  • C++中的this指针、访问控制和构造函数
    C++中的this指针、访问控制和构造函数this指针在C++中,this指针是一个特殊的指针,它指向当前对象的地址。每个非静态成员函数(包括成员函数模板)都有一个this指针作为其隐含参数,这意味着在成员函数内部,this可以用来引用调用该成员函数的对象。this指针是自动传递给成员函数的,......
  • Android第一行代码——快速入门 Kotlin 编程(2.5 面向对象编程)
    目录2.5    面向对象编程2.5.1    类与对象2.5.2    继承与构造函数2.5.3    接口2.5.4    数据类与单列类2.5    面向对象编程        和很多现代高级语言一样,Kotlin也是面向对象的,因此理解什么是面向对......
  • C++ 面试100问--完结(十一)
    C++中虚函数是怎么实现的?        每一个含有虚函数的类都至少有有一个与之对应的虚函数表,其中存放着该类所有虚函数对应的函数指针(地址),类的示例对象不包含虚函数表,只有虚指针;派生类会生成一个兼容基类的虚函数表。C++中纯虚函数的引入有什么目的?        纯......
  • 判断对象是否为空对象的方式
    //判断对象为空对象//1.Object.keys()letobj={}functiongetObjLength(){console.log(Object.keys(obj).length===0)//true}getObjLength()//2.Object.entries()functionisEmptyObj(......
  • C++学习笔记——004
    字符'0'和'\0'及整数0的区别:字符型变量用于存储一个单一字符,在C语言中用char表示,其中每个字符变量都会占用1个字节(8位二进制数)。字符'0':charc='0'; 它的ASCII码实际上是48,内存中存放表示:00110000。字符'\0':ASCII码为0,表示一个字符串结束的标志。这是转......
  • C++面试100问(十一)
    C++中STL中map和set的原理是什么?        map和set的底层实现主要通过红黑树来实现。C++中static和const的区别是什么?        const强调值不能被修改,而static强调唯一的拷贝。C++中关键字static有什么作用?        1)函数体内:static修饰的局部变......
  • 新书速览|轻松学C++编程:案例教学
    零负担学习C++语言的语法,轻松上手C++面向对象程序设计本书简介The19thAsianGames《轻松学C++编程:案例教学》从初学者的角度循序渐进地从C++语言的基础语法到高级语法进行讲解。全书使用生动的实例和图示,介绍C++面向对象程序设计的基础知识;进而通过案例详解类的高级应......
  • C++学习笔记——003
    malloc() 函数在C语言中就出现了,在C++中仍然存在,但建议尽量不要使用malloc()函数。new与malloc()函数相比,其主要的优点是,new不只是分配了内存,它还创建了对象。//一维数组动态分配,数组长度为mint*array=newint[m];//释放内存delete[]array;//二维数......