个人主页:Lei宝啊
愿所有美好如期而遇
目录
再谈构造函数
构造函数体赋值
初始化列表
explicit关键字
Static成员
概念
特性
友元
友元函数
友元类
内部类
匿名对象
拷贝对象时的一些编译器优化
再次理解封装
练习题
再谈构造函数
构造函数体赋值
class Date
{public:Date(int year = 2023, int month = 10, int day = 30){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;};
我们通过构造函数可以给对象赋值,但是不能称为初始化,因为初始化只能初始化一次,而构造函数函数体中可以进行多次赋值。
初始化列表
class Date
{public:Date(int year = 2023, int month = 10, int day = 30):_year(year),_month(month),_day(day){}private:int _year;int _month;int _day;};
注意:
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
class A
{public:A(int a = 0):_a(a){}private:int _a;int _b;};class B
{public:B(int& a,int b,A& c):_a(a),_b(b),_c(c){}private:int& _a;const int _b;A _c;};int main()
{int a = 6;int b = 3;A c;B b(a,b,c);return 0;
}
- f具有常属性,不可修改。
- 引用在声明时就需要初始化,而且之后不可以修改。
- 自定义类型在没有默认构造函数的时候,我们有没有给参数,或者说他在类里声明,我们都需要把他加进初始化列表中进行初始化。
class A
{public:A(int num = 0):_a(num){}A(const A& _A){_a = _A._a;_b = _A._b;}int Getnum_a(){return _a;}private:int _a;int _b;
};int main()
{//会先去调用默认构造函数,然后在构造之前走初始化列表//将a._a初始化为0A a;cout << a.Getnum_a() << endl;return 0;
}
class A
{public://这里初始化顺序不是按先写就先初始化 //而是按照声明顺序进行初始化A(int a):_a2(a),_a1(_a2){}void print() const{cout << _a1 << " " << _a2 << endl;}private:int _a1;int _a2;};int main()
{A a(1);a.print();return 0;
}
explicit关键字
class Date
{public:Date(int year):_year(year){}Date(int year, int month, int day):_year(year),_month(month),_day(day){}void print() const{cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;};int main()
{//C++支持单个参数的构造函数这样使用//先用2023去构造临时对象,该临时对象再进行和对象b的拷贝构造Date date1 = 2023;date1.print();//C++11支持多参数这样使用,和上面是相同的道理Date date2 = { 2023,10,30 };date2.print();return 0;
}
class Date
{public:Date(int year):_year(year){}Date(int year, int month, int day):_year(year),_month(month),_day(day){}void print(){cout << _year << "-" << _month << "-" << _day << endl;}void print() const{cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;};int main()
{Date date1 = 2023;date1.print();Date date2 = { 2023,10,30 };date2.print();const Date date3 = { 2023,10,31 };date3.print();return 0;
}
但是这样写可读性不好,有没有看着别扭的感觉,所以如果我们不想让这样的方式能通过编译,就使用explicit关键字修饰该构造函数。
class Date
{public:explicit Date(int year):_year(year){}explicit Date(int year, int month, int day):_year(year), _month(month), _day(day){}void print(){cout << _year << "-" << _month << "-" << _day << endl;}void print() const{cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;};int main()
{Date date1 = 2023;date1.print();Date date2 = { 2023,10,30 };date2.print();//用这三个数去构造一个临时对象,然后拷贝构造const Date date3 = { 2023,10,31 };date3.print();return 0;
}
加上explicit后就不允许这样构造对象了。
我们再举一个其他栗子:
class Date
{public:Date(int year,int month,int day):_year(year),_month(month),_day(day){cout << "Date()" << endl;}private:int _year;int _month;int _day;};int main()
{const Date& a = { 2023,11,2 };return 0;
}
解释:我们用三个参数去构造一个临时对象,a就是这个临时变量的别名,如果我们不加const就会报错,因为临时变量具有常性,而我们先前说过权限放大和缩小的问题,一个const类型的对象被非const类型引用,会出现权限放大,所以我们要加上const。
Static成员
我们先来抛出一个问题,假如我们想要计算一共有几个对象被构造,那么如何计算?我们有如下几个方案,判断他们的可行性。
方案一:搞一个全局变量,在每一次的调用构造函数时++。
方案二:在类里定义一个计数的变量。
接下来我们来看他们是否可行:
先看方案一实现的代码:
#include <iostream>
using namespace std;int count = 0;class A
{
public :A():_a(1),_b(2){count++;}void print() const{cout << _a << " " << _b << endl;}private:int _a;int _b;};void func(A a)
{//...
}int main()
{A a;func(a);return 0;
}
结果不明确是因为我们使用了using namespce std;展开了该命名空间,而该命名空间里有函数的名字也叫做count,所以就出现了结果不明确,当然,我们可以换个名字,不是非要用count,而且,我不展开std命名空间不可以吗,我只展开部分,比如cout或者endl展开就好,难道不可以吗?是的,都可以。
接着看方案二:
class A
{
public:A():_a(1), _b(2){count++;}void print() const{cout << _a << " " << _b << endl;}private:int _a;int _b;int count;};void func(A a)
{//...
}int main()
{A a;func(a);return 0;
}
这样可以吗?显然不行,每个对象都有count,这样的话,不管哪个count,都只是1,都只构造一次。
那么有没有更好的解决方案呢?有的,就是我们接下来要说的static成员。
概念
- 声明为static的类成员称为类的静态成员。
- 用static修饰的成员变量,称之为静态成员变量;
- 用static修饰的成员函数,称之为静态成员函数。
- 静态成员变量一定要在类外进行初始化。
class A
{public:A(){_count++;}A(const A& a){_count++;}static int Get_count(){return _count;}private:static int _count;};//这里才是_count的定义
int A::_count = 0;void func(A a)
{//...
}int main()
{A a;A b(a);func(a);cout << A::Get_count() << endl;return 0;
}
结果为3。
特性
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区,所以在使用时要加作用域限定符,比如:A::Get_count()
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明,比如:int A::_count = 0;
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员.
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
友元
友元函数
我们现在可以去尝试重载operator<<运算符重载了。
如果我们将这个重载写在类体内,那么必然会有this指针去占到参数的第一个位置,那么我们再调用cout时流插入流入的方向就变成流向对象了,看例子:
#include <iostream>
using namespace std;class Date
{public:Date(int year = 2023, int month = 11, int day = 2){_year = year;_month = month;_day = day;}void print(){cout << _year << "-" << _month << "_" << _day << endl;}ostream& operator<<(ostream& out){out << _year << "-" << _month << "-" << _day;return out;}private:int _year;int _month;int _day;};int main()
{Date a;a << cout;}
这样重载<<运算符是不是很别扭,非常奇怪,所以注定了我们要将他重载到类外,也就是全局,
那么我们将其写在全局后,又因为这几个成员变量都是私有的,我们在类外无法访问到,我们当然可以写他们的Get函数得到他们的值,但是这样比较麻烦,而且C++不喜欢这样做,java才喜欢,C++会去搞一个友元函数。
直接看用法。
#include <iostream>
using namespace std;class Date
{//在函数前加friend表示是友元函数,虽然我们将其声明在类内,但这个函数不属于类friend ostream& operator<<(ostream& out, const Date& d);public:Date(int year = 2023, int month = 11, int day = 2){_year = year;_month = month;_day = day;}void print(){cout << _year << "-" << _month << "_" << _day << endl;}private:int _year;int _month;int _day;};ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "-" << d._month << "_" << d._day << endl;return out;
}int main()
{Date a;cout << a;}
这样我们重载的函数成为了Date类的友元函数,就可以访问Date私有的成员变量。
友元类
#include <iostream>
using namespace std;class Date
{friend class Time;public:Date(int year = 2023, int month = 11, int day = 2){_year = year;_month = month;_day = day;}void print() const{cout << _year << "-" << _month << "_" << _day << endl;}private:int _year;int _month;int _day;};class Time
{public:Time(int hour = 11, int minute = 54, int second = 0):_hour(hour),_minute(minute),_second(second){}void Print_Date_Time() const{cout << _d._year << "-" << _d._month << "-" << _d._day;cout << ":";//cout << _hour << "-" << _minute << "-" << _second << endl;printf("%d-%d-%02d\n", _hour, _minute, _second);}private:int _hour;int _minute;int _second;Date _d;
};int main()
{Time t;t.Print_Date_Time();}
我们要注意Time是Date的友元,Time可以访问Date的私有的成员变量,但是Date不能访问Time私有的成员变量,有句话说的好,我把你当朋友,但是你不一定把我当朋友,就是这个道理。
内部类
首先说明C++中很少使用内部类,java是喜欢使用内部类的,我们只需要明白他的语法,能够看懂,明白需要注意的地方就好,很少使用内部类。
#include <iostream>
using namespace std;class A
{public:A(){_count++;}A(const A& a){_count++;}static int Get_count(){return _count;}class B{public:B(){_count++;_a = 3;}private:int _a;};private:int _A;static int _count;};int A::_count = 0;int main()
{cout << sizeof(A) << endl;return 0;
}
我们可以看到计算A的大小时不包括B,可以这么理解,B是一个独立的类,只是受到A的访问限定符的限制,并且B天生就是A的友元,可以访问A的私有变量。
两个点:
- A,B是独立的两个类,只是B受到A访问限定符的限制。
- B天生就是A的友元。
其余不再多说。
匿名对象
直接看示例:
#include <iostream>
using namespace std;class A
{public:A(){_count++;}A(const A& a){_count++;}static int Get_count(){return _count;}class B{public:B(){_count++;_a = 3;}private:int _a;};private:int _A;static int _count;};int A::_count = 0;int main()
{cout << sizeof(A) << endl;//匿名对象调用成员函数cout << A().Get_count() << endl;return 0;
}
匿名对象就是没有名字的对象,生命周期只在本行。语法就是类名加括号表示匿名对象。
拷贝对象时的一些编译器优化
class Date
{public:Date(int year = 2023, int month = 11, int day = 3):_year(year), _month(month), _day(day),count(0){_count++;count = _count;cout << "构造-" << count << endl;}Date(const Date& a):count(0){_count++;count = _count;cout << "拷贝构造-" << count << endl;}~Date(){cout << "析构-" << count << endl;}Date& operator=(const Date& a){cout << "赋值" << endl;return *this;}private:int _year;int _month;int _day;int count;static int _count;
};int Date::_count = 0;void func(Date aa)
{}Date func()
{Date a;return a;
}
接下来我们来写测试用例:
测试用例一:
int main()
{Date a;a = 1;return 0;
}
首先是a构造,接着临时对象构造,临时对象赋值给a,临时对象生命周期只在本行,临时对象析构,主函数结束a析构。
int main()
{//隐式类型转换,首先是1先构造一个临时对象,接着是//临时对象拷贝构造aDate a = 1;return 0;
}
所以这就是编译器的优化,将构造加拷贝构造优化为--->构造,只有在一行上编译器才会优化。
而且只有比较新的编译器才会这么去优化,老版本的编译器是不会优化的。
至于为什么不是赋值而是拷贝构造,是因为一个存在的对象给一个将要创建的对象,是拷贝构造,而一个存在的对象给一个也已经存在的对象才是赋值。
void func(Date aa)
{}int main()
{Date a;func(a);return 0;
}
void func(Date aa)
{}int main()
{//2构造匿名对象,匿名对象拷贝构造aafunc(Date(2));return 0;
}
一行上,构造+拷贝构造优化为--->构造
void func(Date aa)
{}int main()
{//隐式类型转换,3构造临时对象,临时对象拷贝构造aafunc(3);return 0;
}
同样被优化为只有构造。
测试用例二:
Date func()
{Date aa;return aa;
}int main()
{Date a;a = func();return 0;
}
a构造,aa构造,接着返回值拷贝构造临时对象,aa析构,临时对象赋值给a,如果不优化,应该是这样:
- 构造-1
- 构造-2
- 拷贝构造-3
- 析构-2
- 赋值
- 析构-3
- 析构-1
Date func()
{Date aa;return aa;
}int main()
{Date a = func();return 0;
}
这个优化更厉害了,构造,拷贝构造+拷贝构造,最后优化到只剩构造。
再次理解封装
我们用洗衣机来举例,就像我们知道洗衣机的一系列属性,将这些属性写到一个类里,这个类就是用来描述洗衣机属性的,但他是洗衣机吗?不是,用这些属性实例化出来的对象才是洗衣机,就像我们现实生活中尽管我们清楚洗衣机的属性,但是不买一个也没有办法去洗衣服。
练习题
求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)
class Sum
{
public:Sum(){_i++;_sum += _i; }static int Get_sum(){return _sum;}private:static int _i;static int _sum;};int Sum::_i = 0;
int Sum::_sum = 0;class Solution
{
public:int Sum_Solution(int n) {Sum arr[n];return Sum::Get_sum();}
};
日期差值_牛客题霸_牛客网 (nowcoder.com)
#include <iostream>
using namespace std;class Sub
{
public:Sub(int date):_date(date){}int GetMonth(int year,int month) const;int operator-(Sub& d);private:int _date;int _year;int _month;int _day;
};int main()
{int Date1,Date2;cin >> Date1 >> Date2;int temp;if(Date1 > Date2){temp = Date1;Date1 = Date2;Date2 = temp;}Sub d1(Date1);Sub d2(Date2);cout << (d1-d2) << endl;return 0;
}
// 64 位输出请用 printf("%lld")int Sub::operator-(Sub& d)
{_year = _date/10000;_month = _date/100%100;_day = _date%100;d._year = d._date/10000;d._month = d._date/100%100;d._day = d._date%100;int month = 1;int day = 1;int total = 0;if(_year == d._year){if(_month == d._month){return abs(_day-d._day+1);}else {int month = 1;int total_1 = 0;while(month < _month){total_1 += GetMonth(_year,month);}total_1 += _day;month = 1;int total_2 = 0;while(month < d._month) {total_2 += GetMonth(_year,d._month);}total_2 += d._day;return abs(total_1 - total_2 + 1);}}else {int month = 1;int total_1 = 0;while(month < _month){total_1 += GetMonth(_year,month);month++;}total_1 += _day;total_1 = 365 - total_1;if(_month>2 && _year%4==0 && _year%100!=0 || _year%400==0){total_1--;}month = 1;int total_2 = 0;while(month < d._month) {total_2 += GetMonth(_year,month);month++;}total_2 += d._day;total = total_1 + total_2;if(d._year - _year > 1){for(int i=_year+1; i<d._year; i++){if(_year%4==0 && _year%100!=0 || _year%400==0){total++;}}total += 365 * (d._year-1);}}return total + 1;}int Sub::GetMonth(int year,int month) const
{int month_day[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};if(month == 2 && (_year%4==0 && _year%100!=0 || _year%400==0)){return 29;}return month_day[month];
}
打印日期_牛客题霸_牛客网 (nowcoder.com)
#include <iostream>
using namespace std;int main()
{int year;int n;int month_day[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};while(scanf("%d %d",&year,&n) != EOF){int month = 1;int day = 1;while(n > month_day[month]){n -= month_day[month];month++;}if(month > 2 && (year%4 ==0 && year%100 != 0 || year%400 == 0)){n--;if(n == 0){month--;n = 29;}}day = n;printf("%d-%02d-%02d\n",year,month,day);}}
// 64 位输出请用 printf("%lld")
日期累加_牛客题霸_牛客网 (nowcoder.com)
#include <iostream>
using namespace std;int main()
{int n;cin >> n;int year,month,day,Add_Day;int month_day[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};while(n--){cin >> year >> month >> day >> Add_Day;int count = 0;while(count < Add_Day){count++;day++;int num = month_day[month];if(month == 2 && (year%4 == 0 && year%100 != 0 || year % 400 == 0)){num++;}if(day > num){day = 1;month++;}if(month == 13){month = 1;year++;}}printf("%d-%02d-%02d\n",year,month,day);}}
// 64 位输出请用 printf("%lld")