网站建设 招标文件,南昌做网络推广的,深圳时事热点新闻,搬家公司电话号码17 初始化列表
17.1 初始化列表的引入
之前我们给成员进行初始化时#xff0c;采用的是下面的这种方式#xff1a;
class Date
{
public:Date(int year, int month, int day)//构造函数{_year year;_month month;_day day;}
private:int _year;int _month;int _day;
};…17 初始化列表
17.1 初始化列表的引入
之前我们给成员进行初始化时采用的是下面的这种方式
class Date
{
public:Date(int year, int month, int day)//构造函数{_year year;_month month;_day day;}
private:int _year;int _month;int _day;
};虽然上述构造函数调用之后对象中已经有了一个初始值但是实际上并不能将其称为对对象中成员变量进行初始化因为构造函数体中的语句只能将其称为赋初值而不能称作初始化。因为初始化只能初始化一次而构造函数体内可以多次赋值。
例
#include iostream
using namespace std;
class A
{
public:int _a1;int _a2;//const int _x;//const int _x 1;
};
int main()
{A aa;return 0;
}加const int _x;前运行结果 加const int _x;后运行结果 加const int _x 1;后运行结果 为什么加了const int _x;这条语句后运行起来编译器就报错了呢这是因为const变量必须在定义的位置初始化而A aa;是整体定义的地方并不能在那里对_x进行初始化虽然我们可以将语句修改为const int _x 1;但是这样做的实质并不是初始化而是给了_x一个缺省值而且这个特性只有在C11之后才有那么在C11之前该怎么办呢所以说必须给每个成员变量找一个定义的位置不然像const这样的成员将不好处理。
所以为了解决这样的问题C引入了初始化列表这样的方式。
17.2 初始化列表的特性
初始化列表由以一个冒号开始接着是一个以逗号分隔的数据成员列表每个成员变量后面跟一个放在括号中的初始值或表达式。
例
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};哪个对象调用构造函数初始化列表就是它所有成员变量定义的位置。不管成员变量是否在初始化列表中显示编译器都会对每个成员变量进行定义。
例
#include iostream
using namespace std;
class A
{
public:A():_x(1),_a2(1){_a1;_a2--;}void Print(){cout _a1 _a2 _x endl;}int _a1 2;int _a2 2;const int _x;
};
int main()
{A aa;aa.Print();return 0;
}输出结果 调试结果 从调试结果可以看到在上述例子中当初始化列表对_a1进行初始化时由于_a1未在初始化列表中显式设置所以使用了缺省值对其进行初始化而对_a2、_x则直接用( )中的值进行初始化。等初始化完成后初始化列表再去执行{ }中的操作。
类中包含以下成员必须放在初始化列表位置进行初始化 引用成员变量const成员变量自定义类型成员变量且该类没有默认构造函数时
例
#include iostream
using namespace std;
class A
{
public:A(int a)//不是A的默认构造函数:_a(1){}void Print(){cout _a;}
private:int _a;
};
class B
{
public:B(int a 1, int ref 1): _aobj(1), _ref(ref), _n(10){}void Print(){_aobj.Print();cout _ref _n endl;}
private:A _aobj; // 没有默认构造函数int _ref; // 引用const int _n; // const
};int main()
{B bb;bb.Print();return 0;
}初始化列表中无_aobj(1)时运行结果 初始化列表中无_ref(ref)时运行结果 初始化列表中无_n(10)时运行结果 正常运行结果 结论尽量使用初始化列表初始化因为不管是否使用初始化列表对于自定义类型的成员变量一定会先使用初始化列表进行初始化。
每个成员变量在初始化列表中只能出现一次因为只能初始化一次。成员变量在类中声明的次序就是其在初始化列表中的初始化顺序与其在初始化列表中的先后次序无关。
例
#include iostream
using namespace std;
class A
{
public:A(int a):_a1(a), _a2(_a1){}void Print() {cout _a1 _a2 endl;}
private:int _a2;int _a1;
};
int main()
{A aa(1);//构造函数aa.Print();//
}输出结果 从输出结果可以看到由于_a2在类中声明的次序在_a1的前面所以_a2会比_a1先初始化而_a2在初始化列表中又是用_a1的值进行初始化_a1在没有被初始化之前又是随机值所以_a2初始化所得到的也是随机值。
18 explicit关键字
18.1 explicit关键字的引入
引入explicit关键字之前我们先来看下面一段代码
#include iostream
using namespace std;
class A
{
public:A(int a):_a1(a){}void Print() {cout _a1 _a2 endl;}
private:int _a2;int _a1;
};
int main()
{A aa1(1);//构造函数A aa2 1;//编译能通过吗aa1.Print();aa2.Print();return 0;
}运行结果 从输出结果可以看到A aa2 1;这条语句的左右两边明明不是同一个类型编译却通过了这是因为这里发生了隐式的类型转换。
也就是说A aa2 1;这条语句会先生成一个1的具有常性的临时变量将这个临时变量的类型转换为A后再用来给aa2初始化。
例
#include iostream
using namespace std;
class A
{
public:A(int a):_a1(a){}void Print() const{cout _a1 _a2 endl;}
private:int _a2;int _a1;
};
int main()
{A aa1(1);//构造函数const A ref 10;//类型转换//A ref 10;ref.Print();return 0;
}用const修饰A ref的运行结果 不用const修饰A ref的运行结果 从输出结果可以看到由于类型转换时生成的临时变量具有常性如果被赋值的对象不具有常性的话编译器就会报错。
实际上构造函数不仅可以构造与初始化对象对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数还具有类型转换的作用。
如果不想让这种类型转换发生就需要引入explicit关键字。
18.2 explicit关键字的特性
对于单参构造函数使用explicit修饰后将禁止类型转换。对于第一个参数无默认值其余均有默认值的构造函数使用explicit修饰后也将禁止类型转换。
例
#include iostream
using namespace std;
class Date
{
public:// 1. 单参构造函数没有使用explicit修饰具有类型转换作用Date(int year):_year(year){}// 2. 虽然有多个参数但是创建对象时后两个参数有默认值使用explicit修饰禁止类型转换/*explicit Date(int year, int month 1, int day 1): _year(year), _month(month), _day(day){}*/Date operator(const Date d){if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;}
private:int _year;int _month;int _day;
};
void Test()
{Date d1(2022);// 用一个整形变量给日期类型对象赋值// 实际编译器背后会用2023构造一个临时对象而后用临时对象给d1对象进行赋值d1 2023;
}
int main()
{Test();return 0;
}屏蔽单参构造函数后的运行结果 屏蔽多参构造函数后的运行结果 19 static成员
19.1 static成员的引入
引入static成员之前我们先来看一道面试题。
题目实现一个类计算程序中创建出了多少个类对象。
在我们学C语言的时候可以通过定义一个全局变量count来计数但到了C之后如果还用这样的方式那么类的封装性就无法体现了所以在C中引入了static成员来解决这个问题。
C规定声明为static的类成员称为类的静态成员用static修饰的成员变量称之为静态成员变量用static修饰的成员函数称之为静态成员函数。
19.2 static成员的特性
静态成员变量一定要在类外进行定义和初始化定义时不添加static关键字在类中只起声明作用。静态成员为所有类对象所共享它不属于某个具体的对象而是存放在静态区。类静态成员的访问方式 类名::静态成员对象.静态成员 静态成员函数没有隐藏的this指针不能访问任何非静态成员。静态成员也是类的成员受public、protected、private访问限定符的限制。
掌握了static成员的特性我们就可以用它来解答刚才的问题了。
#include iostream
using namespace std;
class A
{
public:A() { _scount; }A(const A t) { _scount; }~A() { --_scount; }int GetACount() { return _scount; }
private:static int _scount;
};
int A::_scount 0;
int main()
{A a1, a2;A a3(a1);A* ptr nullptr;cout a1.GetACount() endl;cout a2.GetACount() endl;cout ptr-GetACount() endl;return 0;
}运行结果 从输出结果可以看到不仅用对象.静态成员的方式可以访问到静态成员当对象的指针为空时也可以进行访问。
那类名::静态成员这样的访问方式有什么应用场景呢
我们可以考虑这样一个问题当我们想知道一个函数内部创建了多少个对象时该怎么做呢
由于这个时候对象是在函数内部创建的那么我们在函数外部再使用对象.静态成员的方式进行访问就明显不合适了那该怎么办呢
有人提出了下面这种方法
#include iostream
using namespace std;
class A
{
public:A(int a 0) { _scount; }A(const A t) { _scount; }int GetACount() { return _scount; }
private:static int _scount;
};
int A::_scount 0;
void Test()
{A a1 1, a2 1;A a3(a1);
}
int main()
{Test();A a4;cout a4.GetACount()-1 endl;return 0;
}运行结果 从输出结果可以看到这种方法通过在函数外再创建一个对象然后用这个对象去访问静态成员之后再减1就得到了函数内部所创建对象的个数。
这个方法虽然能够达到效果但是总归是有点撇脚的有没有什么更好的办法呢
这个时候就可以考虑用类名::静态成员的方式来进行访问。
采用这种方式的话我们就需要用static把GetACount函数修饰为静态成员函数由于静态成员函数没有this指针所以它就可以通过指定类域来进行调用。
#include iostream
using namespace std;
class A
{
public:A(int a 0) { _scount; }A(const A t) { _scount; }static int GetACount() { return _scount; }
private:static int _scount;
};
int A::_scount 0;
void Test()
{A a1 1, a2 1;A a3(a1);
}
int main()
{Test();cout A::GetACount() endl;return 0;
}运行结果 19.3 练习 这道题本身其实不难可是题目要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句A?B:C就让问题比较棘手了。
那么在这里其实我们就可以利用static的特性通过在一个类里面声明静态成员变量_i和_sum一个用于自增一个用于求和。而后在类里面定义一个求和函数Sum让其实现每调用一次_sum就加_i同时_i自增以实现等差求和。
#include iostream
using namespace std;
class Sum
{
public:Sum(){_sum _i;_i;}static int GetSum()//用于获取私有成员_sum{return _sum;}
private:static int _i;static int _sum;
};
int Sum::_i 1;
int Sum::_sum 0;class Solution
{
public:int Sum_Solution(int n){Sum a[n];return Sum::GetSum();}
};需要注意的是由于部分老版的编译器不支持变长数组所以在编译器上运行时可能会报错但是在oj上是可以正常通过的。
20 友元
20.1 友元的引入
之前我们想把一个日期类Date输入是采用这样的方式
#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 d1(2023, 10, 5);d1.Print();return 0;
}运行结果 这样虽然能够实现输出的功能但每次都要通过对象去调用Print函数才能实现有没有什么办法能够像内置类型那样直接用cout输出呢
有人想到如果能将流插入运算符重载那样就好办了我们不妨来试一下
#include iostream
using namespace std;
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}ostream operator(ostream _cout, const Date d){_cout d._year - d._month - d._day endl;return _cout;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2023, 10, 5);cout d1 endl;return 0;
}运行结果 编译器报错了这是什么原因呢
实际上这是因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置this指针默认是第一个参数也就是左操作数但是实际使用中cout需要是第一个形参对象如果要将operator重载为成员函数当前就只能通过下面的方式
#include iostream
using namespace std;
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}ostream operator(ostream _cout){_cout _year - _month - _day endl;return _cout;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2023, 10, 5);d1 cout endl;// d1 cout; - d1.operator(d1, cout);return 0;
}运行结果 虽然这样确实比刚才调用Print函数要方便了但这明显是不符合常规的调用逻辑的。
要让cout是第一个形参对象还有个方法就是将operator重载成全局函数。
#include iostream
using namespace std;
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}
//private:int _year;int _month;int _day;
};
ostream operator(ostream _cout, const Date d)
{_cout d._year - d._month - d._day endl;return _cout;
}
int main()
{Date d1(2023, 10, 5);cout d1 endl;return 0;
}运行结果 这个时候好像就能满足我们的要求了但是新的问题又出现了类外要访问成员只能将成员变为公有但这样一来封装性又无法得到保证了。
要解决这个问题此时就需要友元来解决。
20.2 友元函数的特性
友元函数是定义在类外部的普通函数不属于任何类但需要在类的内部声明声明时需要加friend关键字。友元函数可访问类的私有和保护成员但不是类的成员函数。友元函数可以在类定义的任何地方声明不受类访问限定符的限制。一个函数可以是多个类的友元函数。友元函数的调用与普通函数的调用原理相同。当模板函数作为类模板的友元函数时不能像普通函数那样在类里面友元声明函数名即可要直接在类模板中定义友元函数。
知道了以上友元函数的特性我们不仅可以用cout来输出自定义类型还可以用cin来输入自定义类型。
#include iostream
using namespace std;
class Date
{friend ostream operator(ostream _cout, const Date d);friend istream operator(istream _cin, Date d);
public:Date(int year 1900, int month 1, int day 1): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
ostream operator(ostream _cout, const Date d)
{_cout d._year - d._month - d._day;return _cout;
}
istream operator(istream _cin, Date d)
{_cin d._year;_cin d._month;_cin d._day;return _cin;
}
int main()
{Date d;cin d;cout d endl;return 0;
}运行结果 20.3 友元类的特性
友元类的所有成员函数都可以是另一个类的友元函数且都可以访问另一个类中的非公有成员。
关于友元类有以下几点特性
友元关系是单向的不具有交换性。比如上述的Date类如果我们还想再加一个Time类并在Time类中声明Date类为其友元类那么可以在Date类中直接访问Time类的私有成员变量但想在Time类中访问Date类中私有成员变量则不行。友元关系不能传递。也就是说如果C是B的友元B是A的友元则不能说明C是A的友元。友元关系不能继承。该特性会在后续讲到继承的时候再详细介绍
例
#include iostream
using namespace std;
class Time
{friend class Date; // 声明日期类为时间类的友元类则在Date类中就可以直接访问Time类中的私有成员变量friend ostream operator(ostream _cout, const Date d);friend istream operator(istream _cin, Date d);
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
{friend ostream operator(ostream _cout, const Date d);friend istream operator(istream _cin, Date d);
public:Date(int year 1900, int month 1, int day 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接访问时间类私有的成员变量_t._hour hour;_t._minute minute;_t._second second;}private:int _year;int _month;int _day;Time _t;
};
ostream operator(ostream _cout, const Date d)
{_cout d._year 年 d._month 月 d._day 日 d._t._hour 时 d._t._minute 分 d._t._second 秒 endl;return _cout;
}
istream operator(istream _cin, Date d)
{_cin d._year;_cin d._month;_cin d._day;_cin d._t._hour;_cin d._t._minute;_cin d._t._second;return _cin;
}
int main()
{Date d1;cin d1;cout d1;
}运行结果