c++入门:类的流插入流提取、初始化列表、编译器优化等

目录

一.流插入、流提取

二.友元函数

三.内部类

四.初始化列表

五.静态对象

六.匿名对象

七.编译器优化


一.流插入、流提取

        流插入cout是ostream类型的对象,流提取cin是istream类型的对象。为了方便自定义类型的输入输出,需要我们对'<<'、'>>'进行重载。

        要注意的是类的成员函数都有一个隐藏参数this指针作为第一个参数,如果按照这个逻辑写的话就是

ostream& operator<<(ostream& out) {
	printf("%4d年%02d月%02d日", _year, _month, _day);
	cout << endl;
	return out;
}
int main() {
	Date d1(2024,2,21);
	d1.operator<< (cout);
	return 0;
}

        这样写必须要保证日期类的对象是第一个参数,所以必须写在左边,这和常用的<<不符合。那能在声明形参时把this指针顺序放在后面吗?不能!!!但是又不能把他做成全局的(一是会改变原来的含义,二是类外不允许访问私有成员)

        只有一个办法:友元声明。把全局函数变成类的好友,让他可以使用类的所有成员,但是没有this指针,所以能实现正常的流插入顺序。

        友元声明就是在函数之前加一个friend,并且函数写在类里面。

        提个问题:为什么要返回ostream类型的引用?

        想一想上期讲的赋值重载。

  或许有人会有疑问:为什么要返回该对象的引用呢?如果我们写成这样

void operator=(const Date& d) {
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

        那么如何解释d1=d2=d3?显然,要实现完整的功能,连续赋值,就要返回被赋值的对象

        那再想想,能不能传值返回?显然对于d1=d2=d3是可以的,但是如果遇上(d1=d2)=d3呢?要知道传值返回是返回创建的临时对象,而临时变量是不能做左值的(不能被修改),所以必须返回引用,使其能做被被修改的左值。

        所以,类似地,为了解决连续流插入的问题,需要返回控制台一直做第一个参数

cout << d1 << d2 << endl;

        流提取也是同理,重载并友元声明 :

friend istream& operator>>(istream& in, Date& d) {
	cin >> d._year >> d._month >> d._day;
	return in;
}
Date d2();
cin >> d2;

二.友元函数

        接着上面提出的话题讲,关于友元函数和友元类。

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

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

  • 友元函数可以访问类的私有成员,但不是类的成员函数
  • 友元函数不能加const限制
  • 友元函数可以在类定义的任何地方声明,不受访问限定符的限制
  • 一个函数可以是多个类的友元函数(这个函数可以用很多资源)
  • 友元函数的调用和普通函数的调用一样

三.内部类

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

        内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访
问外部类中的所有成员。但是外部类不是内部类的友元。(你拿别人当朋友,别人不拿你当朋友)

class A {
public:
	class B {
	private:
		int _b;
	};
private:
	int _a;
};

         内部类和全局类的定义没区别,sizeof外部类大小不计算内部类。所以sizeof(A)是4不是8。 

四.初始化列表

        1.初始化列表 

        调用构造函数时什么时候是定义成员变量?

Date(int year = 2024, int month = 2, int day = 21)
{
	_year = year;
	_month = month;
	_day = day;
}

        是在函数体内部吗?那这样写算不算重定义呢? 

Date(int year = 2024, int month = 2, int day = 21)
{
	_year = year;
	_month = month;
	_day = day;
	_year = 1;
}

         如果是重定义的话就会报错。但是没有。原来,c++祖师爷为了解决这个问题发明了初始化列表的写法。就是在构造函数函数体前用括号内的值对成员变量初始化,这里也就是变量最开始定义的地方

Date(int year = 2024, int month = 2, int day = 21)
	:_year(year)
	,_month(month)
	,_day(day)
{}

         注意在构造函数时有几个地方可以缺省,但是有不同的含义和顺序。

class Date {
private:
//声明
	int _year=2005;
	int _month=12;
	int _day = 18;//这里是作为初始化的缺省,如果初始化没有给值,就用缺省值
//如果没有缺省值就得到随机值
	
public:
	Date(int year = 2024, int month = 2, int day = 21)//定义的形参是函数体赋值修改的缺省
		:_year(2)
		, _month(3)
//_day没有给值,就用缺省值18初始化
	{
		_year = year;
		_month = month;
		_day = day;//函数体内部时对已经初始化的变量赋值修改
	}
};
//构造结果是2024——2——21;

 列表初始化有以下几点特征:

  1. 初始化的顺序是变量声明的顺序
  2. 就算没写括号外面的也要先走括号外面的(得到随机值)
  3. 先初始化,再赋值修改
  4. 引用、const变量、自定义类型成员(且该类没有默认构造函数)必须初始化(必须给值)

        无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。总结一下,不需要传参就可以调用构造函数,都可以叫默认构造函数。所以如果不传参时写了构造函数却不写缺省值编译器就会报错。

class A
{
public:
	A(int a) {
		_a = a;
	}
private:
	int _a;
};
class Date {
private:
	int _year=2005;
	int _month=12;
	int _day = 18;
	A _aa;
public:
	Date(int year = 2024, int month = 2, int day = 21)
		:_year(2)
		, _month(3)
		,_aa(23)
	{}
};

 2.隐式类型转换

        c++支持单参数隐式类型转换

class A {
private:
	int _a;
public:
	A(int a = 10) :_a(a)
	{}
};
int main() {
	A aa1(2);
	A aa2 = 4;

    int i=8;
    double d=i;
	return 0;
}

 

        隐式类型转换:4会先构造一个对象,然后再拷贝构造。

        但是结果不是这样,编译器并不会调用拷贝构造,而是直接调用构造函数。这是编译器的一个优化。一般连续几个步骤的构造会被合二为一

五.静态对象

        静态变量只会申请一次,声明写在类里面,不是某一个对象的成员,而是所有对象共有的

而定义要单独写,当作是初始化(static变量必须初始化)

        这个可以用来计算创建了多少个对象。

class A {
private:
	static int _n;
};
int A::_n=0;//初始化

比如这道题:求1+2+3+...+n__牛客网

class Sum{
    public:
    Sum(){
        _ret+=_i;
        _i++;
    }
    int getSum(){
        return _ret;
    }
    private:
    static int _i;
    static int _ret;
};

int Sum::_i=1;
int Sum::_ret=0;

class Solution {
public:
    int Sum_Solution(int n) {
        Sum a[n];
        return a[n-1].getSum();
    }
};

我们直接创建n个对象,每次调用析构函数都++i,再求总和。

六.匿名对象

        有名字的对象叫有名对象,没名字的叫匿名对象。匿名对象生命周期只在当前一行。

        意义:生来就是为了调用对象或对象的函数,不方便写名字

七.编译器优化

1.传参

        构造(临时变量)+拷贝构造——》》直接构造

       比如:隐式类型转换:这个是把2构造对象,然后拷贝构造给aa1,优化成了直接构造aa1

 这个是直接引用常变量(临时变量具有常性)

本来构造匿名对象,然后拷贝构造给形参,优化成直接构造。因为匿名对象创建后会销毁,函数形参函数结束后也会销毁,就直接构造。

2.传返回值 

  1. A f1() {
    	A aa;
    	return aa;//一个构造,一个拷贝构造
    }
    int main() {
    	f1();
    	return 0;
    }
  2.  
    A f1() {
    	A aa;
    	return aa;//一个构造,两个拷贝构造————》》一个构造,一个拷贝构造
    }
    int main() {
    	A ret=f1();
    	return 0;
    }

    两个拷贝构造合二为一

  3. A f1() {
    	return A();//一个构造,两个个拷贝构造————》》两个构造!!!
    }
    int main() {
    	A ret=f1();
    	return 0;
    }

    匿名对象直接三合一

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值