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

news/2024/12/21 6:45:46/

个人主页: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初始化为0A 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先构造一个临时对象,接着是//临时对象拷贝构造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. 构造-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")


http://www.ppmy.cn/news/1195330.html

相关文章

高压检测设备

比如&#xff1a;高压数字表、高压差分探头、指针式高压表、电流探枪、高压探棒 这些设备都是用来测量高压的&#xff0c;有的测电压&#xff0c;有的测电流。 高压数字表&#xff1a; 单独使用&#xff0c;功能很简单&#xff0c;有2个正负极探爪&#xff0c;把2个探爪连接到…

三巨头的AI布局:微软领先,谷歌追赶,亚马逊待考

AI是当今科技领域的热门话题&#xff0c;也是三大云计算巨头微软、谷歌和亚马逊的核心战略。近日&#xff0c;微软和谷歌先后公布了2023年第三季度的财报&#xff0c;展示了各自在AI方面的进展和成果。亚马逊也即将发布财报&#xff0c;让我们一起来看看三巨头的AI布局有什么不…

Python中文件copy模块shutil

高级的 文件、文件夹、压缩包 处理模块 shutil.copyfileobj(fsrc, fdst[, length])将文件内容拷贝到另一个文件中 import shutil shutil.copyfileobj(open(old.xml,r), open(new.xml, w)) shutil.copyfile(src, dst)拷贝文件 shutil.copyfile(f1.log, f2.log) #目标文件无需…

React 项目结构小结

React 项目结构小结 简单的记录一下目前 React 项目用的依赖和实现 摸索了大半年了大概构建一套用起来还算轻松的体系……&#xff1f;基本上应该是说可以应对大部分的项目了 使用的依赖 目前项目还在 refactoring 的阶段&#xff0c;所以乱得很&#xff0c;这里是新建一个…

3D数据过滤为2D数据集并渲染

开发环境&#xff1a; Windows 11 家庭中文版Microsoft Visual Studio Community 2019VTK-9.3.0.rc0vtk-example参考代码 代码逻辑&#xff1a;初始化数据集points -> 添加数据集到polydata -> 通过vtkVertexGlyphFilter过滤&#xff08;带顶点、单元数据&#xff09;po…

2023面试知识点三

1、强软弱虚引用 强引用 当内存不足的时候&#xff0c;JVM开始垃圾回收&#xff0c;对于强引用的对象&#xff0c;就算是出现了OOM也不会对该对象进行回收&#xff0c;打死也不回收~&#xff01; 强引用是我们最常见的普通对象引用&#xff0c;只要还有一个强引用指向一个对象…

C语言:选择+编程(每日一练Day9)

目录 选择题&#xff1a; 题一&#xff1a; 题二&#xff1a; 题三&#xff1a; 题四&#xff1a; 题五&#xff1a; 编程题&#xff1a; 题一&#xff1a;自除数 思路一&#xff1a; 题二&#xff1a;除自身以外数组的乘积 思路二&#xff1a; 本人实力有限可能对…

c语言作业2

7-1 判断成绩等级&#xff0c;输入3个科目的成绩&#xff0c;如果三门成绩都大于等于80则为优秀&#xff0c;输出等级为A&#xff1b;其余情况为一般&#xff0c;输出等级为B&#xff1b; 输入格式: 输入在一行中给出3个整数取值在[0,100]&#xff0c;其间以空格分隔。 输出格…