lesson3-C++类和对象(下)

个人主页: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;

};
初始化列表:以一个冒号开始 ,接着是一个以 逗号分隔的数据成员列表 ,每个 " 成员变量 "后面跟
一个放在括号中的初始值或表达式。
下面的大括号里还可以进行一系列赋值或者其他操作,相当于是构造。

注意: 

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。
  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
  • 引用成员变量
  • 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具有常属性,不可修改。
  • 引用在声明时就需要初始化,而且之后不可以修改。
  • 自定义类型在没有默认构造函数的时候,我们有没有给参数,或者说他在类里声明,我们都需要把他加进初始化列表中进行初始化。
3.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,
一定会先使用初始化列表初始化。
其实也就是不管是内置类型还是自定义类型,都会先去调用他的默认构造或者构造函数,在进入构造函数前,先走初始化列表,不管我们是否写了初始化。
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;
}
4. 成员变量 在类中 声明次序 就是其在初始化列表中的 初始化顺序,与其在初始化列表中的先后
次序无关
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指针,不能访问任何非静态成员.
  • 静态成员也是类的成员,受publicprotectedprivate 访问限定符的限制

友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以
友元不宜多用。
友元分为: 友元函数 友元类

友元函数

我们现在可以去尝试重载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;

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

这样我们重载的函数成为了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. 构造-1
  2. 构造-2
  3. 拷贝构造-3
  4. 析构-2
  5. 赋值
  6. 析构-3
  7. 析构-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")

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lei宝啊

觉得博主写的有用就鼓励一下吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值