? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? 在前面的博客已经介绍过类和对象的语法知识,但是它还有一些零碎的语法细节。接下来我们来看看它还有什么要注意的~
目录
再谈构造函数
构造函数体赋值
初始化列表
只能在初始化列表初始化的成员及注意事项
explicit
?static成员
概念
面试题:实现一个类,计算中程序中创建出了多少个类对象。
特性?
【问题】
1. 静态成员函数可以调用非静态成员函数吗? 不可以
2. 非静态成员函数可以调用类的静态成员函数吗? 可以
3. 静态成员函数可以调用非静态成员变量吗? 不可以
4. 非静态成员函数可以调用类的静态成员变量吗? 可以
总结?
C++11 的成员初始化新玩法
友元
友元函数
友元函数的相关说明:
友元类
1、友元关系是单向的,不具有交换性。
?2、友元关系不能传递
?内部类
概念及特性
sizeof(外部类)=外部类,和内部类没有任何关系。
牛客网一道练习题?
再次理解封装
再次面向对象
再谈构造函数
构造函数体赋值
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
在之前我们讲构造函数是这样初始化的:
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, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
只能在初始化列表初始化的成员及注意事项
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次) 2. 类中包含以下成员,必须放在初始化列表位置进行初始化 ? ? @ 引用成员变量
? ? @ const成员变量
? ? @ 自定义类型成员(该类没有默认构造函数)
举个栗子:
class A
{
public:
A(int a) //不传参不能调用该构造函数,不是默认构造函数
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B(int a, int ref)
:_a(a)
, _ref(ref)
, _n(10)
{}
private:
A _a; // 没有默认构造函数
int& _ref; // 引用
const int _n; // const
};
?? ?A _a; //因为不传参不能调用A的构造函数,而它只能在初始化列表中赋值初始化。
?? ?int& _ref; // 由于引用特性有在定义的时候必须初始化,在这里是声明而不是初始化。成员? 变量初始化的地方在构造函数初始化列表的地方进行初始化。(在这里用另一个变量来初始化)
?? ?const int _n; // 我们知道const修饰的变量不能被改变也就不能被赋值了,所以在这里_n必须在定义的时候初始化,在这里是声明,而不是初始化。为了不造成冲突,所以只能在构造函函初始化列表的地方给一个值进行初始化。
3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
我们来演示一下:
? ? ? ? 我们发现上面的代码输出是100,这样我们我们可以确定_hour是后执行_hour=100;的语句的,那么说明先使用初始化列表进行初始化。
4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关 我们来通过一个场景演示一下:
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();
}
上面的代码结果会输出什么?感觉大部分的人认为是输出1 1吧,我们运行一下看看结果:
?按照往常的思维是不是感觉很不可思议。其实这是和初始化顺序有关。
private: //成员变量 ?? ?int _a2;? ?// 先声明,先被初始化 ?? ?int _a1;? //后声明,后被初始化
?? ?A(int a) //构造函数 ?? ??? ?:_a1(a)? //_a1后初始化,传过来的a==1,所以_a1被初始化成1 ?? ??? ?, _a2(_a1)? //_a2先被初始化,由于_a1还没有被初始化,所以_a1的值时随机值,则? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? _a2也是随机值 ?? ?{}
explicit
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。
举个栗子:
class Date
{
public:
Date(int year)
:_year(year)
{}
private:
int _year;
int _month;
int _day;
};
void TestDate()
{
Date d1(2018);
// 用一个整形变量给日期类型对象赋值
// 实际编译器背后会用2019构造一个临时对象,最后用临时对象给d1对象进行赋值
d1 = 2019;
}
? ? ? 上述代码可读性不是很好,用explicit修饰构造函数,将会禁止单参构造函数的隐式转换。实际上在这里编译器会对?? ?d1 = 2019;进行优化,直接优化成一个构造函数,关于细节我会在其他博客来描述~? 希望大家支持。
?static成员
概念
? ? ? ?声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化。
面试题:实现一个类,计算中程序中创建出了多少个类对象。 ?
class A
{
public:
A()
{
++_count;
}
A(const A& t)
{
++_count;
}
static int GetACount()
{
return _count;
}
private:
static int _count;
};
int A::_count = 0; //静态成员变量初始化,需要指定类域
void TestA()
{
cout << A::GetACount() << endl; //调用类里面静态成员函数方法之一
A a1;
A a2;
A a3(a1);
cout << A::GetACount() << endl;
}
int main()
{
TestA();
return 0;
}
这时候定义的静态全局变量是_count,注意在这里只能在类外面进行初始化定义。
用A::GetACount()指定的时候不会再去调用里面的构造函数,得到的值就是最后结果。下面会通过具体OJ题来感受~
运行结果:
特性?
1. 静态成员为所有类对象所共享,不属于某个具体的实例 2. 静态成员变量必须在类外定义,定义时不添加static关键字,并且需要在变量名前指定类域 3. 类静态成员即可用类名::静态成员或者对象.静态成员来访问(只能访问静态成员) 4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员 5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以? ? ? ? 具有返回值
【问题】 1. 静态成员函数可以调用非静态成员函数吗? 不可以
举个栗子:
class Date
{
Date(int year =1,int month =1,int day=1)
:_year(year)
,_month(month)
,_day(day)
{}
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
static int CountDate()
{
Print();
_count++;
}
private:
int _year;
int _month;
int _day;
static int _count;
};
int Date::_count = 0;
int main()
{
return 0;
}
运行如下:
? ? ? ? 我们发现静态成员函数不能调用非静态成员函数,因为静态成员函数没有this指针,不能明确表示哪个对象来调用函数。?
2. 非静态成员函数可以调用类的静态成员函数吗? 可以
举个栗子:
class Date
{
public:
Date(int year =1,int month =1,int day=1)
:_year(year)
,_month(month)
,_day(day)
{}
void Print()
{
HelloPrint();
cout << _year << " " << _month << " " << _day << endl;
}
static void HelloPrint()
{
cout << "hello world" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
d.Print();
return 0;
}
运行结果:
?非静态成员是可以调用静态成员的。
3. 静态成员函数可以调用非静态成员变量吗? 不可以
举个栗子:
class Date
{
public:
Date(int year =1,int month =1,int day=1)
:_year(year)
,_month(month)
,_day(day)
{}
static void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
d.Print();
return 0;
}
运行如下:
?静态成员函数没有this指针,就不能明确指明调用哪个对象里面的成员变量。
4. 非静态成员函数可以调用类的静态成员变量吗? 可以
举个栗子:
class Date
{
public:
Date(int year =1,int month =1,int day=1)
:_year(year)
,_month(month)
,_day(day)
{}
void Print()
{
cout << _count << endl;
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
static int _count;
};
int Date::_count = 0;
int main()
{
Date d;
d.Print();
return 0;
}
运行结果:
总结?
静态成员函数只能调用静态成员函数或成员变量。
非静态成员函数既可以调用静态成员函数、成员变量也可以调用非静态成员函数、变量。
C++11 的成员初始化新玩法
? ? ? ? C++11支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变量缺省值。
举个栗子:
class Date
{
public:
void Print()
{
cout << _year << " " << _month << " " << _day;
}
private:
//非静态成员变量,可以在成员声明时给缺省值
int _year = 2022;
int _month = 3;
int _day = 3;
};
int main()
{
Date d;
d.Print();
return 0;
}
我们来打印输出一下,看看我们给的缺省值是否给成员变量初始化了:
?这样我们发现,我们没有写构造函数,这时候缺省值就赋值给了对应的成员变量。
这个语法场景适用于只给缺省值,仅仅为了使用默认的构造函数赋初始值时候就可以这样来写,我们就不用再自己写构造函数了。
? ? ? ?如果我们写了拷贝构造(这时候必须写构造函数,具体原因之前讲述过了~),但是又想使用编译器默认生成的构造函数就可以这样来定义:
Date() = default;
这表示显示调用编译器默认生成的构造函数。
友元
友元分为:友元函数和友元类 友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度(高内聚,低耦合),破坏了封装,所以友元不宜多用。
友元函数
? ? ? 问题:现在我们尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理。
上面一大段话是什么意思呢?我们用代码演示一下就能明白了:
看下面代码:
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
ostream& operator<<(ostream& _cout)
{
_cout << _year << "-" << _month << "-" << _day;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d(2022, 3, 3);
//cout << d; 正常思维逻辑
d << cout; //然而定义到类内部只能这样使用
return 0;
}
? ? ? ? ?我们发现日期类的<<运算符重载完了,这样我们可以输出日期,但是我们知道,d<<cout会被替换成d.operator<<(&d,_cout),这时候this指针接收了&d,作为左操作符,cout作为右操作符,这时候cout只能写到右边,但是这不符合我们的习惯。
怎么样来解决呢?我们定义到类外面就不会存在抢操作符的情况了。
? ? ? ? 但是我们直接定义到类外面就不能访问类里面的私有成员了,但是也不能把私有成员改成公有,这样会破坏原来的封装性。这时候友元函数就派上用场了:
class Date
{
friend ostream& operator<<(ostream& _cout,const Date& d);
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)//没有了this指针抢位的问题
{
_cout << d._year << "-" <<d. _month << "-" << d._day;
return _cout; //这样支持连续输出_cin也是类似
}
int main()
{
Date d(2022, 3, 3);
//cout << d; 正常思维逻辑
cout << d; //然而定义到类内部只能这样使用
return 0;
}
运行结果:
?我们只要在类里面用friend + 函数声明即可。这样的意思是该函数可以访问类里面的私有和共有成员变量及函数。
注意:
?友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
友元函数的相关说明:
1、友元函数可访问类的私有和保护成员,但不是类的成员函数
2、友元函数不能用const修饰
3、友元函数可以在类定义的任何地方声明,不受类访问限定符限制
4、一个函数可以是多个类的友元函数 5、友元函数的调用与普通函数的调用和原理相同
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员
1、友元关系是单向的,不具有交换性。 ? ? ?比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。 2、友元关系不能传递 ? ? ? 如果B是A的友元,C是B的友元,则不能说明C时A的友元。
1、友元关系是单向的,不具有交换性。
举个栗子:
class Date
{
friend class Time;//Time是Date的友元类,可以访问Date里面的私有成员变量
public:
Date(int year = 2022, int month = 3, int day = 3)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
class Time
{
public:
void Print(const Date& d)
{
cout << d._year << " " << d._month << " " << d._day;
cout << endl;
_d._year = 1;
_d._month = 1;
_d._day = 1;
cout << _d._year << " " << _d._month << " " << _d._day; //这种写法不用传参
}
private:
int _hour;
int _minute;
int _second;
Date _d;
};
int main()
{
Date d;
Time t;
t.Print(d);
return 0;
}
这回收Time是Date的友元类,可以访问Date的私有成员变量。
运行结果:
?2、友元关系不能传递
? ? ? 就上面的例子来说,Time类在Date里面定义了友元,Time可以访问Date的私有成员,但是Date不能访问Date里面的私有成员,因为Date没有在Time类里面定义友元类。
举个栗子:
class Time
{
private:
int _hour = 12;
int _minute = 12;
int _second = 12;
}; //为了能够保证Dtae调用到Time,需要Time定义到Date前面,或者声明一下
class Date
{
friend class Time;
public:
void Print(const Time& t)
{
cout << t._hour << ":" << t._minute << ":" << t._second << endl;
}
};
int main()
{
Date d;
Time t;
d.Print(t);
return 0;
}
?注意一个细节:为了能够保证Dtae调用到Time,需要Time定义到Date前面,或者声明一下。
但是在类里面定义函数时不用考虑先后顺序,也不用提前声明。
我们运行一下上面的代码看是否能过:
?我们这就可以发现 Date不能访问Time类里面的私有成员,友元类只有单向传递性。
Time类在Date里面定义了友元,Time可以访问Date的私有成员,但是Date不能访问Date里面的私有成员,因为Date没有在Time类里面定义友元类。
?内部类
概念及特性
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。 注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性: 1. 内部类可以定义在外部类的public、protected、private都是可以的。 2. 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。 3. sizeof(外部类)=外部类,和内部类没有任何关系。
sizeof(外部类)=外部类,和内部类没有任何关系。
class Date
{
public:
Date(int year = 2022, int month = 3, int day = 3)
: _year(year)
, _month(month)
, _day(day)
{}
class Time
{
public:
void Print(const Date& d)
{
cout << d._year << " " << d._month << " " << d._day;
}
private:
int _hour;
int _minute;
int _second;
};
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
cout << sizeof(d) << endl;
Date::Time t;
cout << sizeof(t) << endl;
return 0;
}
这里我们要注意,定义Time类的一个对象时,一定要指明外部类类域,就比如:?Date::Time t;
计算外部类的大小时,内部类不参与;计算内部类的大小时,外部类不参与;
运行结果如下:
牛客网一道练习题?
求1+2+3+...+n_牛客题霸_牛客网
? ? ? ? ? ? ? ? ? ? ? ??
代码:
class Sum
{
public:
Sum()
{
_ret += _i;
_i++;
}
static int GetN()
{
return _ret;
}
static void Init()
{
_i = 1;
_ret = 0;
}
private:
static int _i;
static int _ret;
};
int Sum::_i = 1;
int Sum::_ret = 0;
class Solution {
public:
int Sum_Solution(int n)
{
Sum::Init();
Sum arr[n];//Sum* p = new Sum[n];
return Sum::GetN();
}
};
? ? ?class Solution { public: ? ? int Sum_Solution(int n) ? ? { ? ? ? ? Sum::Init(); //为了支持多次运算,每次计算时要把_i和_ret恢复初始值 ? ? ? ? Sum arr[n];//Sum* p = new Sum[n]; 构造函数执行n次 ? ? ? ? return Sum::GetN(); ? ? } };
?我们不用静态成员时:
? ? ? ? ? ??
return Sum().GetN()-(n+1);这时候Sum()匿名对象又调用了一次构造函数,相当于多加了一次n+1,在最后减掉就可以了。
如果使用return Sum::GetN()就不会存在上面的问题了(这次调用函数不会自动调用构造函数,因为构造函数是非静态成员函数)。
再次理解封装
C++是基于面向对象的程序,面向对象有三大特性即:封装、继承、多态。 C++通过类,将一个对象的属性与行为结合在一起,使其更符合人们对于一件事物的认知,将属于该对象的所有东西打包在一起;通过访问限定符选择性的将其部分功能开放出来与其他对象进行交互,而对于对象内部的一些实现细节,外部用户不需要知道,知道了有些情况下也没用,反而增加了使用或者维护的难度,让整个事情复杂化。
下面举个例子来让大家更好的理解封装性带来的好处,比如:乘火车出行
我们来看下火车站: 售票系统:负责售票----用户凭票进入,对号入座 工作人员:售票、咨询、安检、保全、卫生等 火车:带用户到目的地
火车站中所有工作人员配合起来,才能让大家坐车有条不紊的进行,不需要知道火车的构造,票务系统是如何操作的,只要能正常方便的应用即可。?
?想想下,如果是没有任何管理的开放性站台呢?火车站没有围墙,站内火车管理调度也是随意,乘车也没有规矩,比如:
封装其实就是一种更为严格的管理。
通过访问限定符把想给别人访问的设置成公有,不想给别人访问的设置成私有或保护。
这样大家都按照一定的规则来使用,这样有效减少了使用者各种各样的问题。
再次面向对象
面向对象的理解还是需要我们慢慢去感受的。?
|