个人主页: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初始化为0
A 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先构造一个临时对象,接着是
//临时对象拷贝构造a
Date a = 1;
return 0;
}
所以这就是编译器的优化,将构造加拷贝构造优化为--->构造,只有在一行上编译器才会优化。
而且只有比较新的编译器才会这么去优化,老版本的编译器是不会优化的。
至于为什么不是赋值而是拷贝构造,是因为一个存在的对象给一个将要创建的对象,是拷贝构造,而一个存在的对象给一个也已经存在的对象才是赋值。
void func(Date aa)
{
}
int main()
{
Date a;
func(a);
return 0;
}
void func(Date aa)
{
}
int main()
{
//2构造匿名对象,匿名对象拷贝构造aa
func(Date(2));
return 0;
}
一行上,构造+拷贝构造优化为--->构造
void func(Date aa)
{
}
int main()
{
//隐式类型转换,3构造临时对象,临时对象拷贝构造aa
func(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")