打怪升级:第37天 |
---|
![]() |
在上一篇文章中我们详细讲解了类与对象的主要内容,那是一块难啃的骨头,
不过还好,我们已经跨越了重重阻碍走到了这最后一步,这里呢我们对类与对象的一些边边角角进行补充,这关打完–原地飞升。
class A
{
public:A(int a1 = 1, int a2 = 1){ _a1 = a1;_a2 = a2;}private:int _a1;int _a2;
};int main()
{A aa;return 0;
}
上面这种是我们常用的构造类的方式:类中有两个成员变量,通过构造函数进行赋值;
class A
{
public:A(int a1 = 1, int a2 = 1, const int c = 1){_a1 = a1;_a2 = a2;_c = c;}private:int _a1;int _a2;const int _c;
};int main()
{A aa;return 0;
}
为了解决有些成员变量必须初始化问题,我们的祖师爷当时引入了 初始化列表的概念,
用法:在拷贝构造中使用,
以一个冒号开始,接着是以逗号分割的数据成员列表,每个成员变量后面跟着一个放在括号内的初始值或表达式。
class A
{friend ostream& operator<<(ostream& out, A& aa);public:A(int a1 = 100, int a2 = 1, const int c = 1): _a1(a1) // 初始化列表 进行初始化操作, _a2(a2), _c(c) { }private:int _a1;int _a2;const int _c;
};ostream& operator<<(ostream& out, A& aa)
{out << aa._a1 << " " << aa._a2 << " " << aa._c << endl;return out;
}int main()
{A aa;cout << aa;return 0;
}
运行结果:
注意:
- 写在初始化列表中是初始化, 每个成员变量只可以初始化一次,
写在构造函数内部是赋值, 变量的赋值可以进行任意次。
- 必须初始化的成员变量有三种:
①const成员变量;
②引用成员变量;
③自定义类型成员变量(并且自定义类型无默认构造函数 – 无参构造)
class A
{private:int a_val = 20;
};class B
{
public:B(int d1, int d2) // 有参构造{b_data1 = d1;b_data2 = d2;}private:int b_data1;int b_data2;
};class C
{
public:C() : b(10, 10) {}private:A a;B b;
};int main()
{C c;return 0;
}
- 尽量使用初始化列表来初始化成员变量,因为不管使用不使用初始化列表,对于自定义类型都会自动走一遍初始化列表,先使用初始化列表进行初始化。
这里我们就可以更好地理解:构造函数对内置类型不作处理,对自定义类型会调用它的默认构造函数,
对自定义类型默认构造函数的调用是在初始化列表里进行的。
- 成员变量在类中的声明次序就是其在初始化列表中的初始化次序,与它在初始化列表中的次序无关。
// 我们来看一下 第 4条
class AA
{friend ostream& operator<<(ostream& out, const AA& a);
public:AA(): _data2(10),_data1(_data2){}private:int _data1;int _data2;
};ostream& operator<<(ostream& out, const AA& a)
{out << a._data1 << " " << a._data2 << endl;return out;
}int main()
{AA a;cout << a;return 0;
}
深入理解构造函数的初始化和赋值
这一部分确实绕来绕去不好理解,不过c++也并非一开始就是这样的,例如第二部分的:初始化缺省值 是在C11的时候才加入的,目的是为了方便对自定义变量进行初始化,前辈们各种各样的增添功能,虽然使得我们这些初学者觉得头痛不已,但是当我们真正掌握了这些内容之后,使用起来那就是“纵享丝滑”,希望大家以后都可以“祖师爷附体”、“如有神助”。
构造函数不仅可以构造和初始化对象,对于单个参数或者第一个参数无默认值的构造函数还具有类型转换的作用。
void Test01()
{int a = 10;float f = 1.0;char c = 'a';// 隐式类型转换:赋值运算符两边的数据类型不同时,编译器会创建一个临时变量,// 将赋值运算符右边的数据转换为运算符左边的数据类型,放入临时变量中,// 之后使用临时变量对左边的数据进行赋值;// 注意:临时变量具有常性。int tmp = a;cout << tmp << endl;tmp = f;cout << tmp << endl;tmp = c;cout << tmp << endl;
}
class A
{friend ostream& operator<<(ostream& out, A& aa);public:A(int a1, int a2 = 1):_a1(a1),_a2(a2){ cout << "A(int, int)" << endl;}A(const A& aa){_a1 = aa._a1;_a2 = aa._a2;cout << "A(const A&)" << endl;}~A(){cout << "~A()" << endl;}private:int _a1;int _a2;
};ostream& operator<<(ostream& out, A& aa)
{out << aa._a1 << " " << aa._a2 << endl;return out;
}
class A
{friend ostream& operator<<(ostream& out, A& aa);public:A(int a1 = 10, int a2 = 1):_a1(a1),_a2(a2){ cout << "A(int, int)" << endl;}A(const A& aa){_a1 = aa._a1;_a2 = aa._a2;cout << "A(const A&)" << endl;}~A(){cout << "~A()" << endl;}A& operator=(const A& aa){_a1 = aa._a1;_a2 = aa._a2;cout << "operator=(const A&)" << endl;return *this;}private:int _a1;int _a2;
};ostream& operator<<(ostream& out, A& aa)
{out << aa._a1 << " " << aa._a2 << endl;return out;
}A Test02A()
{A a;return a;
}void TestExplicit()
{A aa = Test02A();}
static成员分为static成员变量和static成员函数,使用方法就是在函数或者变量前面加上 static
上面的是我们平时使用static的方法,
既然静态成员函数没有隐藏的this指针,那么就无法访问任何非静态成员函数和变量,
它的作用也就被大大减小了,其实,静态成员函数的存在就是为了返回静态成员变量的值,
其他情况基本用不到它,也就是说:静态成员函数因为静态成员变量而存在。
友元分为:友元函数和友元类。
友元提供了一种突破封装的方式,有时提供了便利,但是这种方式破坏了完整性,增加了耦合度,所以友元不宜多用。
友元关键字:friend
使用方法:在类中写出函数声明,并在声明前加上 friend关键字。
友元就是朋友的意思,友元函数可以访问类中的所有成员,类似但不等同于成员函数 – 友元实际上还是普通函数,并没有默认的this指针,只是给了它访问这个类的权限。
#include
using namespace std;class Date
{
public:void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}void operator<<(ostream out){out << _year << "年" << _month << "月" << _day << "日" << endl;}private:int _year = 2023;int _month = 3;int _day = 8;
};void Test02()
{Date d1;cout << d1; // 错误示范}
class Date
{friend ostream& operator<<(ostream& out, const Date& d);
public:// friend ostream& operator<<(ostream& out, const Date& d); // 友元声明可以写在类内任何位置,一般写在最上面,避免和成员函数声明混淆private:int _year = 2023;int _month = 3;int _day = 8;
};ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}void Test02()
{Date d1;//cout << d1;//d1 << cout; // 成员函数使用形式cout << d1 << endl << d1 << endl; // 全局函数使用形式}
// 下方是两个类:Date、Timeclass Time
{public:Time(int hour = 0, int minute = 0, int second = 0):_hour(hour), _minute(minute), _second(second){}private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year = 0, int month = 0, int day = 0):_year(year),_month(month),_day(day),_t(7,20,0){}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;cout << _hour << "时" << _minute << "分" << _second << "秒" << endl;}private:int _year;int _month;int _day;Time _t;
};void Test05()
{Date d(2023, 3, 10);d.Print();
}
_t 的数据在初始化阶段就已经确定,我们之后想要改变 _t 的数据是办不到的,
这样当然就很不方便,我们想要在Date类中直接改变 _t 的数据就需要 Date类可以访问 Time类中的非公有成员;
class Time
{friend class Date; // 声明Date是Time的友元类public:Time(int hour = 0, int minute = 0, int second = 0):_hour(hour), _minute(minute), _second(second){}void Print(){cout << _hour << "时" << _minute << "分" << _second << "秒" << endl;}private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year=0, int month=0, int day=0):_year(year),_month(month),_day(day),_t(7,20,0){}void SetTime(int hour, int minute, int second){// 直接访问时间类私有的成员变量_t._hour = hour;_t._minute = minute;_t._second = second;}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;_t.Print();}private:int _year;int _month;int _day;Time _t;
};
概念:如果一个类在另一个类的内部声明,这个内部类就叫做内部类。
class A
{
public:class B // 内部类{public:void Print(){cout << "_date = " << _date << endl;}private:int _date = 10;};private:int _val1;int _val2;
};void Test06()
{/*cout << "sizeof(A) = " << sizeof(A) << endl;*/A::B b;b.Print();}
匿名既不写名字;
匿名对象就是没有名字的对象,匿名对象的生命周期只有一行。
更多的使用场景我们以后会继续补充。
静态成员可以直接使用类名进行访问,因为静态成员存在于静态区;
普通成员函数不可以使用类名直接访问(为什么?成员函数不是存在于代码段吗?),因为普通成员函数会默认传this指针,
因此必须创建变量来使用它的this指针,而静态成员函数没有this指针,
当然,我们也可以使用nullptr指针去访问成员函数,仅限成员函数,否则如果访问成员变量会出现空指针解引用;
也可以使用匿名对象。