C++入门之类和对象(下)
文章目录
一、初始化列表
1.1 概念
先来看看构造函数
#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 << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d(2024, 4, 18);
d.Print();
return 0;
}
以上代码中的代码中的构造函数准确来说并不是给类中的成员初始化,只是给类中的成员赋一个初始值,初始化只能初始化一次,而构造函数体内可以多次赋值
这里就引出了一个概念,给类中的成员变量初始化的方法
初始化列表:以一个 冒号 :开始,同逗号 ,隔开每一个成员变量,每个成员变量后跟一个( ) ,括号中的内容就是初始的值(可以是初始值也可以是表达式)
1.2 注意事项 1
#include <iostream>
using namespace std;
class Date
{
public:
Date(int y)
:_year(2024)
, _month(4)
, _day(18)
, xx(10) //const成员必须在初始化列表初始化
,yy(y) //引用也必须在初始化列表初始化
,time(20,20,20) //没有默认构造函数的自定义类型也必须在初始化列表初始化
{
//_year = 2024; //与上面写法等价
//_month = 4;
//_day = 18;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
const int xx;
int& yy;
Time time;
};
class Time
{
public:
Time(int hours, int minutes, int seconds)
{
_hours = hours;
_minutes = minutes;
_seconds = seconds;
}
private:
int _hours;
int _minutes;
int _seconds;
};
int main()
{
int y = 0;
Date d(y);
d.Print();
return 0;
}
初始化列表本质可以理解每个对象中的成员定义的地方
注意事项1:
所用的成员的可以在初始化列表中初始化,也可以在函数体中初始化(也就是大括号里),但是有三种特殊情况
1. 引用修饰的成员变量 (引用必须在定义时初始化)
2. const修饰的成员变量 (const修饰的变量也必须在定义时初始化)
3. 没有默认构造函数的自定义类型 (需要显式传参才可以调用)
1.3 注意事项 2
#include <iostream>
using namespace std;
class Date
{
public:
Date()
:_a1(10)
,_a2(20)
{
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _a1;
int _a2;
//在定义时给一个缺省值
int _year = 2024;
int _month = 4;
int _day = 18;
};
int main()
{
Date d;
d.Print();
return 0;
}
注意事项2:
1. 就算没写初始化列表,每个成员变量都会走一遍(有编译器实现)
2. 内置类型(int char等),会看看是否有缺省值,有缺省值用缺省值,如果没有,取决于编译器是否对内置类型进行处理
3. 自定义类型(class struct等),会调用默认的构造函数,如果没有编译器则会报错
4. 先走初始化列表,再走函数体
5. 建议:尽量使用初始化列表初始化,如果不好在初始化列表中初始化的再在函数体中实现
1.4 注意事项 3
#include <iostream>
using namespace std;
class A
{
public:
A(int x)
:_a1(x) //x赋值给a1
,_a2(_a1) //_a1赋值给a2
{
}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main()
{
A aaa(10);
aaa.Print();
return 0;
}
代码运行结果: 10 随机值
出现随机值的主要原因是因为初始化时并不是按初始化列表顺序初始化的,而是变量在声明的顺序,在上述代码中,_a2先声明,_a1后声明,在初始化时,会先初始化_a2,而此时_a1是随机值,之后再通过x赋值_a1
注意事项3:
初始化顺序与初始化列表顺序无关,与声明顺序有关
二、explicit关键字
2.1 为什么要有explicit关键字
构造函数在接收参数时,还具有类型转换的作用
#include <iostream>
using namespace std;
class A
{
public:
//单参数构造
//explicit A(int x) 为了防止类型转换可以在函数名前面加上explicit关键字
A(int x)
:_a(x)
{
cout << "A(int x)" << endl;
}
//多参数构造
A(int x, int y)
:_a(0)
,_a1(x)
,_a2(y)
{
cout << "A(int x, int y)" << endl;
}
//拷贝构造
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a;
int _a1;
int _a2;
};
int main()
{
A a(10); //构造
A b = a; //拷贝构造
A c = 10; //隐式类型转换,将int类型构造出一个A自定义类型的临时对象,再用这个对象拷贝构造给c
const A& d = 10; //与上述同理,临时对象的拷贝构造,由于临时对象具有常性(只能读,不能修改),需要用const修饰,否则就是权限的放大
//与单参数拷贝构造一致
A e(1, 2);
A f = { 1,2 };
const A& g = { 1, 2 };
return 0;
}
变量 a : 调用构造函数
变量 b : 调用拷贝构造函数
变量 c : 存在隐式类型转换,将内置类型转换成自定义类型,在语法上,先将10构造出一个A的临时对象,再将这个临时对象拷贝构造给c 但是编译器在遇到,连续调用构造函数和拷贝构造函数时,会优化为直接构造
变量d : 与变量c一致,不过由于引用的是临时对象,临时对象具有常性(只能读,不能修改),如果直接引用存在权限的放大(可读可修改),所以要加上const,让其只读
变量e f g是多参数构造,与单参数构造一致,在碰到连续调用构造函数和拷贝构造函数时,会优化为直接构造,其次赋值时要使用花括号
讲这么多还是没提到explicit关键字的作用
由于构造函数会存在类型转换的情况,为了防止这种情况呢,可以在函数名前面加上explicit关键字
三、static成员
3.1 static修饰类的成员和成员函数
计算创建了多少个对象
#include <iostream>
using namespace std;
class A
{
public:
A()
{
++_count;
}
A(const A& x)
{
++_count;
}
//static修饰的函数中没有this指针
static int GetCount()
{
return _count;
}
private:
int _a;
static int _count;
};
int A::_count = 0;
int main()
{
A arr[10];
cout << A::GetCount() << endl;
return 0;
}
注意事项:
- staric修饰成员变量
类似与函数声明与定义分离,staric修饰成员变量在类中声明,在类外面初始化,
类中:static int _count;
类外:int A::_count = 0;
不能给缺省值,因为缺省值是给初始化列表的,而static修饰的变量是存放在静态区的,不在对象中
属于整个类,属于所有对象- static修饰成员函数
由于是在类中被private修饰了,类外面无法拿到,且只能通过static修饰的成员函数来使用,可以写一个static成员函数来获取static成员变量的值
static修饰的成员函数是没有this指针的,所以只能访问static成员变量