C++ :类 和 对象 ※重点※

目录

默认成员函数:

构造函数:

构造函数的特性:

 内置、自定义类型?

  构造函数的小细节:

一:

二:    

  三:

析构函数:

 拷贝构造函数:

运算符重载:

重载前置++后置++?

重载流插入流提取?


基础:带你初步了解 类和对象 的基本机制_luck++的博客-CSDN博客

这一篇能帮助你更好地理解这里的内容!

默认成员函数:

   默认成员函数分为6种,他们都是如果我们不写,编译器会自动生成的函数,如果写了,编译器就不会生成了

 前面四个是非常重要的,后面两个用得不多

构造函数:

   我们在平常写一个类得时候,就怕我们没有给他进行初始化,我们再去访问就是随机值了,怎么解决这种不确定性呢,这个时候我们得默认构造函数就出来了

构造函数的特性:

构造函数是一种特殊的成员函数,构造函数的主要任务不是去创建对象,而是去初始化对象。

  1. 函数名与类名相同。
  2. 没有返回值。
  3. 对象实例化的时候会自动调用构造函数
  4. 可以重载

这些是构造函数的特性,来看看他写出来是什么样吧:

class stu
{
public:
	stu()
	{
		_a = 10;
		_c = 'A';
		_b = 20;
	}

	stu(int a, char c, int b)
	{
		_a = a;
		_c = c;
		_b = b;
	}

	void test1()
	{
		cout << _a << "-" << _c << "-" << _b << endl;
	}

private:
	int _a;
	char _c;
	int _b;
};

   这里就是一个重载,构造函数的参数也是可以加缺省值的,建议呢是全部都加上缺省值,这样也可以防止传参的时候写错而出些错误(注意给缺省值的顺序是从右往左给的) 


 如果你想调用一个不用传参的构造函数,定义的时候应该是这样的:

class stu
{
public:
	stu()
	{
		_a = 10;
		_c = 'A';
		_b = 20;
	}

	stu(int a, char c, int b)
	{
		_a = a;
		_c = c;
		_b = b;
	}

	void test1()
	{
		cout << _a << "-" << _c << "-" << _b << endl;
	}

private:
	int _a;
	char _c;
	int _b;
};

int main()
{
	stu s1;
	stu s3(4, 'C', 40);


}

    大家看见没有,这里是不需要加括号的,加上括号反而会出错,编译器会认为你定义那里并没有定义出一个对象,无法区分是一个函数的声明还是对象的定义;传参的时候也是在对象的后面直接传参的。

    还有一个小小的细节:我们的全缺省的构造函数和自己写的无参的构造函数不要同时存在,虽说他们在语法上,不使用这个类,是可以编译通过的,但是一旦我们使用,用这个类定义一个不传参的对象,这就会报错了,因为两个构造函数都是可以不用传参可以直接调用的,编译器这个时候就不知道用哪个了,然后就报错了

所以我平常写的时候,如果有需要,都是定义为全缺省的,既方便也不容易出错

 内置、自定义类型?

我们要学习默认构造函数,就要知道内置类型和自定义类型,内置类型就是我们平常经常用的那些类型,自定义类型顾名思义就是我们自己定义的类型

 我们编译器默认生成的构造函数对内置类型不做处理,对于自定义类型会去调用它的构造函数:

class date
{

public:
    date()
    {
        _a=100;
    }

private:
    int _a;
};

class stu
{
public:

	void print()
	{
		cout << _a << "-" << _c << "-" << _b << endl;
	}

private:
	int _a;
	char _c;
	int _b;
    
    date a1;
};

int main()
{
	stu s3

}


  对于初始化是区分化处理的,我的内置类型得不到初始化除非自己显示得去写,大家有没有觉得挺奇怪的呢?所以有了一个这样的总结:

   如果一个类中的成员全是自定义类型,我们就用自己默认生成的就够了,有内置成员、或者要显示传参,就要自己写了。


 对于这种处理方式呢,后面在c++11里面给了一个解决办法就是这样:

class date
{

public:
    date()
    {
        _a=100;
    }

private:
    int _a;
};

class stu
{
public:

	void print()
	{
		cout << _a << "-" << _c << "-" << _b << endl;
	}

private:
	int _a = 10;
	char _c = 'A';
	int _b = 20;
    
    date a1;
};

int main()
{
	stu s3

}

   这里给的不是一个赋值,这里依旧只是声明,我们给的值是一个缺省值,是给我们构造函数的初始化列表使用的,如果我们我自己写了构造函数,并给内置类型赋值了,就不会用这些给的缺省值了,大家学到后面初始化列表就知道怎么回事了 


  构造函数的小细节:

一:

我们自己写的构造函数对于自定义类型也是会处理的


二:    

   注意:无参的构造函数和全缺省的构造函数也是可以叫做默认构造函数的(他们和编译器默认生成的都是不需要传参的),但是默认构造函数只能有一个且必须有一个(也可以是半缺省)六大成员函数包括编译器自动生成的默认构造,我们上面说过无参的和全缺省的不能写在一起报错就是这样的:


  三:

    没有默认构造可用的情况就是这样的:

class stu
{
public:

	stu(int a ,char c='A',int b=20)
	{
		_a = a;
		_c = c;
		_b = b;
	}

	void print()
	{
		cout << _a << "-" << _c << "-" << _b << endl;
	}

private:
	int _a;
	char _c;
	int _b;

};

int main()
{
	stu s1;
}

 这里就是编译器因为你自己写了,就没有生成默认的,但是你自己写的,你也没用,就报错了!!!


析构函数:

   析构函数同样也是特殊的成员函数,首先它的功能是和构造函数相反的,但他不是去销毁对象,对象销毁的工作是由编译器去做的,析构函数是去完成资源的清理工作,在对象销毁的时候自动调用

析构函数的特性:

  1. 函数名是在类名前面加上 ‘~’ 
  2. 无参数且无返回值
  3. 一个类有且只有一个析构函数,如果没有显示定义编译器会自动生成
  4. 一个对象生命周期结束,自动调用析构函数 

大家看我下面的代码:我定义的对象里面的那三个成员变量会用到默认生成的析构函数嘛 

class stu
{
public:

	stu(int a = 10,char c = 'A',int b = 20)
	{
		_a = a;
		_c = c;
		_b = b;
	}

	void print()
	{
		cout << _a << "-" << _c << "-" << _b << endl;
	}

private:
	int _a;
	char _c;
	int _b;

};

int main()
{
	stu s1;
}

    这三个成员变量是不是我们的对象销毁的时候,它会自动的销毁啊,那我析构函数是去完成资源的清理的,那是不是malloc 那几个函数 还有new 以及fopen 这些才是析构函数要处理的阿,

   注意:我们编译器默认生成的析构函数对malloc那些函数开辟的空间是不会自动去释放的,这些是需要我们自己手动去写的,这也是因为它无法判断一个指针到底是不是一块内存空间的地址,默认生成的析构函数对于内置类型不做处理,自定义类型调用它的析构函数:


class stu
{
public:

	stu(int a = 10,char c = 'A',int b = 20)
	{
		_a = a;
		_c = c;
		_b = b;
	}

	void print()
	{
		cout << _a << "-" << _c << "-" << _b << endl;
	}

private:
	int _a;
	char _c;
	int _b;

};

int main()
{
	stu s1(20,'B',30);
    stu s2(40,'C',50);
}

大家觉得这里的 s1和s2 谁先析构呢? 

当然是s2会先析构 ,记得:析构顺序和构造的顺序是相反的


 拷贝构造函数:

   这个也是一个特殊的构造函数,它是构造函数的一种重载,它是拿一个已经定义好的对象去定义另外一个对象,同样他也是默认成员函数的一名,不写编译器自动生成: 

   对于内置类型(指针也是)会完成值(浅)拷贝,自定义类型会去调用它的拷贝构造(自己写的也会调用,哪怕你的函数里面没有写它的拷贝)

class stu
{
public:
    stu(stu& d)
    {
        _a = d._a;
		_c = d._c;
		_b = d._b;
    }
    
	stu(int a = 10,char c = 'A',int b = 20)
	{
		_a = a;
		_c = c;
		_b = b;
	}

	void print()
	{
		cout << _a << "-" << _c << "-" << _b << endl;
	}

private:
	int _a;
	char _c;
	int _b;

};

int main()
{
	stu s1(20,'B',30);
    stu s2(s1);//s2 s3都是调用的拷贝构造函数
    stu s3 = s2;
}

 注意:拷贝构造函数的参数只有一个并且必须使用引用传参,如果是传值传参会发生无限递归


    对于那写内置类型的拷贝,都是按字节直接拷贝过去的,对于自定义类型,拷贝初始化规定要调用拷贝构造函数,至于其中的原因就涉及深浅拷贝的问题,就好像我malloc的空间的指针,如果直接赋值(浅拷贝),我两个对象的指针都指向的是同一块空间,那我在调用析构函数的时候,同一块空间被释放两次不就出问题了嘛,我们就得自己去实现拷贝构造

如果我们拷贝的成员里面又数组会报错嘛? :答案是不会的,我们数组拷贝又不是把数组的地址拷贝过去,拷贝的是数组里面的内容

我们的拷贝构造函数同样也是构造函数,他是我们平常写的构造函数的一种重载,我们如果类里面写了拷贝构造函数,那么编译器默认生成的构造函数就不会生成了!


还有一个问题就是涉及const的问题,通常我们在写的时候,如果自己写错了,写成了对 d对象 的成员变量赋值,那不就出错了嘛,所以我们要在拷贝构造的参数用const修饰一下,其实像这样我们不会改变的对象,加上const是最好的

class stu
{
public:
    stu(const stu& d)
    {
        _a = d._a;
		_c = d._c;
		_b = d._b;
    }
    
	stu(int a = 10,char c = 'A',int b = 20)
	{
		_a = a;
		_c = c;
		_b = b;
	}

	void print()
	{
		cout << _a << "-" << _c << "-" << _b << endl;
	}

private:
	int _a;
	char _c;
	int _b;

};

int main()
{
	stu s1(20,'B',30);
    stu s2(s1);//s2 s3都是调用的拷贝构造函数
    stu s3 = s2;
}

运算符重载:

内置类型是直接支持比较和运算的,那我们的自定义类型可以随意的去运算比较嘛?是不是不能阿,所以就有了运算符重载(这样还可以提高可读性):

 函数名:关键字operator后面接需要重载的运算符

参数:它是跟操作数一起变化的

返回值:运算符运算后的结果

注意:不能通过链接其他不是运算符的符号来创建一个新的操作符

有这五个运算符是不能重载的:(  ::  sizeof   ?:   .   .*  )

class stu
{
public:
    stu(const stu& d)
    {
        _year = d._year;
		_month = d._month;
		_day = d._day;
    }
    
	stu(int year = 2022,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

    bool operator==(stu& d1,stu& d2);
private:
	int _year;
	int _month;
	int _day;

};

bool stu::operator==(stu& d1,stu& d2)
{
    return d1._year==d2._year && d1._month==d2._month && d1._day==d2._day;
}

int main()
{
	stu s1(2000,6,9);
    stu s2(2011,5,19);
    if(s1==s2)//这里也是可以这样写的,operator(s1,s2),但这样太别扭了,跟调用函数一样
    {
        cout<<"YES"<<endl;
    }
} 

但是我们上面这段代码是有问题的:

这是因为我们在类里面定义,是会有一个this指针的,正确的定义是这样的:

class stu
{
public:
	stu(const stu& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	stu(int year = 2022, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	bool operator==(stu& d1);
private:
	int _year;
	int _month;
	int _day;

};

bool stu::operator==(stu& d1)
{
	return d1._year == _year && d1._month == _month && d1._day == _day;
}

int main()
{
	stu s1(2000, 6, 9);
	stu s2(2011, 5, 19);
	if (s1 == s2)//这里也是可以这样写的,s1.operator(s2)
    {
		cout << "YES" << endl;
	}
}

  我们的重载也是可以定义为全局的,只不过这样你必须保证你能拿到对象里的成员,可以定义为共有 或者 写个函数去获取(这个是java里面喜欢用的)、写成友元函数:

class stu
{
public:
	stu(const stu& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	stu(int year = 2022, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}


//private:
	int _year;
	int _month;
	int _day;

};

bool operator==(stu& d1,stu& d2)
{
	return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}

int main()
{
	stu s1(2000, 6, 9);
	stu s2(2011, 5, 19);
	if (s1 == s2)//这里也是可以这样写的,operator(s1,s2),但这样太别扭了,跟调用函数一样
	{
		cout << "YES" << endl;
	}
}

 假如我们又有全局也有类里面的,如果全都又它会调用谁呢?

注意这里如果要验证记得拿一个函数的参数加上const,不然编译会报错 :

:调用我们的全局的,我们的编译器会先去类里面找,如果没有再去全局里面找,像上面那种s1.operator(s2)这个是指定了去调用类里面的


现在再来实现一下比较大小吧,大家觉得实现一个 大于 的重载应该怎么写呢

    bool operator>(const stu& d) const
	{
		if ((_year > d._year) || (_year == d._year && _month > d._month)
			|| (_year == d._year && _month == d._month && _day > d._day))
		{
			return true;
		}

		return false;
	}

   这里函数的后面那个const 是为了给this修饰的(不会给其他形参修饰),因为我们的this是不能显示写在形参的位置的,所以有了这样的方法。这里也是因为不涉及值得改变所以才用const来修饰,十分严谨

还有一个小细节:我们在平常写的时候一个运算符可能会涉及一些和它相关的运算符,这些运算符如果写了,就可以直接用这些写好的,更加方便比如 >= 就是 > 和 = 。


 接着来看一下赋值重载:

   stu& operator=(const stu& d)
	{
		if (this != &d)//防止自己给自己赋值
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;

			return *this;
		}
	}

注意这里是有返回值的,我们在赋值完后,可能还会赋值一次,也就是连续赋值,我们这里也是返回的引用,避免了传值返回调用拷贝构造带来的消耗

赋值重载和拷贝构造是不一样的,拷贝构造是去创建一个为定义的对象,赋值重载是两个操作数都存在的赋值

六大成员函数里面是有我们的赋值重载的,那我们自己不写,让编译器自动生成,实现的操作又是什么样的呢?

其实赋值重载参考的是拷贝构造,对于内置类型会完成他的值拷贝 ,自定义类型会去调用它的赋值重载。


重载前置++后置++?

对于重载运算符还有几个要说的点:大家觉得我们的前置++ 和 后置++ 应该怎么写呢?

stu operator++()
stu operator++()

他们函数名相同 返回值也相同,是不是应该用重载来实现阿

stu operator++()
stu operator++(int i)

编译器规定带参数的是后置++ 不带的是前置   并且规定传参是一个整型,传参这个操作我们不用管这个是编译器来帮我们做的

    stu& operator++()
	{
		*this += 1;
		return *this;
	}

	stu operator++(int)
	{
		date tmp(*this);
		*this += 1;
		return tmp;
	}

后置 ++ 返回的是一个临时拷贝,所以不能够返回引用!!!

注意:我们这里的参数 int 是为了让编译器传参,区分是 前置 还是 后置,所以这里是不能是加缺省值的,加了两个函数都可以不传参调用,这样就报错了,这里没有给形参,也是因为我这里只是要告诉编译器这里要传参,不在乎传的值,方便编译器判断。我不写形参的意思就是:这个值不重要,我可以选择不接收或者接收了但我不需要


重载流插入流提取?

流插入:cout <<  流提取:  cin >>

我们的 cin 和 cout都是存在头文件<iostream>,他们分别是两个流的对象,istream->cin,ostream->cout。像我们的内置类型都是已经重载好了的

void operator<<(ostream& out)//延续上面的代码,成员变量依旧是这几个日期
{
	out << _year << '-' << _month << '-' << _day << endl;

}

如果我这个重载写在类里面大家觉得写得对嘛?

   当然肯定是不对的:我们写在类里面他是不是会有一个隐含的this指针,那但我们想正常调用的时候是不是就成这样了:

s1(2022,6,7);
cout<<s1<<endl;//cout.operator<<(s1)

   我的s1是不是就传到out的位置上了这样是不是不对阿,况且我们总不可能因为这样写成:s1<<cout是吧

想要解决这个问题很显然我们是不想要这个this指针的,那我们吧函数写在类外面不就可以了嘛至于访问它的成员变量有多种方法,这里我就选择友元函数来解决(意思是告诉我们这个类这个函数是我们的朋友,可以访问私有成员)

friend ostream& operator<<(ostream& out, const stu& d);

void operator<<(ostream& out, const stu& d)
{
    out << d._year << '-' << d._month << '-' << d._day << endl;

}

 现在这个重载虽可以运行但还有一个问题:我们的返回值是不是应该重新考虑一下呢

我们平常写的时候是有可能会这样写的 cout<<d1<<d2<<d3<<endl; 所以我们输出完一个对象后还得紧接着输出下一个对象:

friend ostream& operator<<(ostream& out, const stu& d);
friend istream& operator>>(istream& in, stu& d);

ostream& operator<<(ostream& out, const stu& d)
{
    out << d._year << '-' << d._month << '-' << d._day << endl;
    return out;
}

istream& operator>>(istream& in, stu& d)
{
	in >> d._year >> d._month >> d._day;

	return in;
}

感谢大家能够看到这里,预祝大家都能收到自己心仪大厂的offer!!!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值