再谈构造函数
对于MyQueue 不需要写它的构造函数,编译器自动生成,会调用它的默认构造。
但是如果Stack类不提供默认构造给你,那就得实现显示调用,该怎么办呢?
有两种办法。
1️⃣构造函数体赋值
在创建对象时,编译器通过调用 构造函数,给对象中各个成员变量一个合适的初始值。
代码示例:
class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; };
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是 不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为 赋初值,而不能称作 初始化。 因为初始化只能初始化一次,而构造函数体内可以多次 赋值 。
如下例子:
class Date { public: Date(int year=2024, int month=1, int day=1)//构造函数初始化,只能初始化一次 //赋值 { _year = year;//可以多次赋值 _year = 2023; _year = 2021; //... _month = month; _day = day; } private: int _year; int _month; int _day; };
2️⃣初始化列表
初始化列表:以一个 冒号 开始,接着是一个以 逗号 分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式。
代码如下:
#include<iostream> using namespace std; class Date { public: Date(int year, int month, int day) : _year(year) , _month(month) , _day(day) { } void Print() { cout << _year << "/" << _month << "/" << _day; } private: int _year; int _month; int _day; }; int main() { Date d1(2024, 1, 1); d1.Print(); return 0; }
执行如下:
【注意】
1. 每个成员变量在初始化列表中 只能出现一次 ( 初始化只能初始化一次 )
2. 类中包含以下成员, 必须放在初始化列表 位置进行初始化:
💦引用成员变量 💦const成员变量 💦自定义类型成员(且该类没有默认构造函数时)
其实可以这样理解:
代码如下:
class Date { public: //初始化列表是每个成员定义的地方 //不管你写不写,每个成员都要走初始化列表 Date(int year, int month, int day, int& i) :_year(year) , _month(month) , _a(i) , _refi(i) ,_x(100)//显示给值了 { //赋值 //_day = day; } void func() { ++_refi; ++_refi; } //private下面如果成员变量右边给了值,都叫做缺省值 private: int _year;//每个成员声明 int _month; int _day; //C++11支持给缺省值,这个缺省值给初始化列表 //如果初始化列表没有显示给值,就用这个缺省值 //必须定义时初始化,也就是说以下这三个成员变量必须出现在初始化列表 const int _x=1;//如果显示给值了,就不用这个缺省值 int& _refi; A _a; }; //能用初始化列表就用初始化列表初始化 //有些场景还是需要初始化列表和函数体混着用 int main() { int n = 0; Date d1(2023, 7, 28, n); d1.func(); cout << n << endl; return 0; }
执行:对_refi就是对着n操作
3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表, 对于自定义类型成员变量, 一定会先使用初始化列表初始化。
#include<iostream> using namespace std; class Time { public: Time(int hour = 0) :_hour(hour) { cout << "Time()" << endl; } private: int _hour; }; class Date { public: Date(int day) {} private: int _day; Time _t; }; int main() { Date d(1); }
执行:
4. 成员变量在类中 声明次序 就是其在初始化列表中的 初始化顺序 , 与其在初始化列表中的先后次序无关
来做一道题:
explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数的 构造函数,还具有类型转换的作用。
class A { public: //explicit A(int i) A(int i) :_a(i) { cout << "A(int i):"<<i<< endl; } A(const A& aa) :_a(aa._a) { cout << "A(const & aa)" << endl; } ~A() { cout << "~A()" << endl; } private: int _a; }; struct SeqList { public: void PushBack(const A& x) { //...扩容 _a[_size++] = x; } size_t size() const { return _size; } //读 const A& operator[](size_t i)const { assert(i < _size); return _a[i]; } //读/写 A& operator[](size_t i) { assert(i < _size); return _a[i]; } private: //C++11 A* _a = (A*)malloc(sizeof(A) * 10); size_t _size = 0; size_t _capacity = 0; }; int main() { A aa1(1); A aa2 = 2; return 0; }
经过编译器优化之后,以下的两个代码是等价的:
A aa1(2);//直接构造 <==> A aa1 = 2
解析:
在早期的编译器中,当遇到下面的一行代码时,会处理成:用2调用A构造函数生成一个临时对象(tmp),再用这个对象(tmp)去拷贝构造aa1
A aa1 = 2;//先构造,再拷贝构造
上面的代码等价于下面这两步: A tmp(2); A aa2(tmp);
但是,C++支持单参数构造函数的隐式类型转换:
编译器会再优化,优化用2直接构造,所以当我们遇到像A aa1 = 2的式子时,实际上编译器已经转换成了A aa1(2),这就叫隐式类型转换
同时在c++中,不想让隐式类型发生,就在构造函数前面加个explicit
因为explicit修饰构造函数,禁止了单参构造函数类型转换的作用
在C语言中我们也讲了隐式类型转换:无论是值拷贝还是说加了引用的,都会生成临时变量的。
static成员
1.static概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量; static修饰的成员函数,称之为静态成员函数。 静态成员变量一定要在类外进行初始化
2.static特性
静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
代码如下:
#include<iostream> using namespace std; class A { public: A() { ++n; ++m; } A(const A& t) { ++n; ++m; } ~A() { --m; } private: int a;//4 byte static int n; static int m; }; int main() { cout << sizeof(A) << endl; return 0; }
2.静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
解析思路:
当我们要计算A这个类,累计创建了多少个对象(用n表示),正在使用的多少个对象(用m表示)
以之前的知识,我们首先会在全局定义两个变量
经过以下代码验证之后,我们发现,如果定义全局的变量,会被外面随意修改
此时的话,我们试下把n和m定义在class A的private内,但是这样每一个对象在定义的时候,都会创建一个n和m,此时n和m是每一个对象的成员了,不是用来统计有几个对象,明显不能这样定义。
这时候如果被static修饰,这两个成员变量就位于静态区了,叫做静态成员变量,需要注意的地方有:
代码如下:
class A { public: private: int a;//4 byte static int n;//静态成员的声明 static int m;//静态成员的声明 }; //在类外面定义 int A::n = 0; int A::m = 0;
还有要注意的;
3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员来访问
静态成员变量被public修饰时:
代码如下:
include<iostream> using namespace std; class A { public: A() { ++n; ++m; } A(const A& t) { ++n; ++m; } ~A() { --m; } //private: static int n; static int m; }; // 静态成员变量的定义初始化 int A::n = 0; int A::m = 0; int main() { A aa1; cout << A::n << " " << A::m << endl;//1.通过类名突破类域进行访问 cout << aa1.n << " " << aa1.m << endl; //2.通过类对象突破类域进行访问 A* ptr = NULL; cout << ptr->n << " " << ptr->m;//3.通过空指针解引用成员突破类域 }
当静态成员变量被private修饰时:
我们当然可以定义被public修饰的成员函数,然后此时被static修饰的两个静态成员n和m通过创建对象aa1,接着aa1.Print()就可以打印出对象的个数,
但如果我定义一个匿名对象,接着调用Print函数,这时候会多出一个n(累计创建的对象),干扰打印的逻辑了。
我们可以借鉴上面静态成员变量突破类域的方式,引出我们静态成员函数的三种突破类域的方式
代码如下:
#include<iostream> using namespace std; class A { public: A() { cout << "A()" << endl; ++n; ++m; } A(const A& t) { ++n; ++m; } ~A() { --m; } //静态成员函数的特点:没有this指针 static int GetM() { return m; } static void Print() { //x++;//不能访问非静态,因为没有this cout << m << " " << n << endl; } private: //这样定义不行,这样的话每一个对象都有一个n和m, //int m; //int n; // 不符合题意,因为这是来统计对象个数的 //静态成员变量属于所有A对象,属于整个类 //声明 //累积创建了多少个对象 static int n; //正在使用的还有多少个对象 static int m; }; int A::n = 0; int A::m = 0; int main() { A aa; //三种突破类域的方式 A::Print();//通过类名调用静态成员函数进行访问 aa.Print();//通过实例化的对象调用成员函数进行访问 A* ptr = NULL; ptr->Print();//通过空指针调用静态成员函数进行访问 return 0; }
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
代码如下:
//4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员 #include<iostream> using namespace std; class A { public: static void Print() { cout << x++ << endl; } private: int x;//非静态成员变量 static int a;//静态成员变量 }; int main() { A x; x.Print(); }
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
【问题】
1. 静态成员函数可以调用非静态成员函数吗?
答:不能 , 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
2. 非静态成员函数可以调用类的静态成员函数吗?
答:可以。 因为静态成员函数和非静态成员函数都在类中,在类中不受访问限定符的限制。
代码如下:
#include<iostream> using namespace std; class A { public: void Notstatic() { Print();//<-------非静态调用调用静态 } //两种写法 static void Print() { cout << a << endl; } /*static int Print() { cout << a << endl; return a; }*/ private: int x;//非静态成员变量 static int a;//静态成员变量 }; int A::a = 10; int main() { A x; x.Notstatic(); }
执行:
【C++初阶】第四站:类和对象(下)(理解+详解)-2