欢迎光临
我们一直在努力

类和对象(下)

目录

(一)再探构造函数

(1)初始化列表

注意点:

(2)缺省值

(3)成员变量初始化逻辑图

(二)类型转换

(1)内置类型 转 自定义类型:=

注意点

(2)内置类型 转 自定义类型:={}

(3)可以用A构造B

(4)加explicit修饰,不支持隐式类型转换

(三)static成员

(1)静态成员变量

注意点

(2)静态成员函数

注意点

(3)oj题

(四)友元

(1)重载输入输出运算符

(2)友元

A.友元函数

特点:

B.友元类

 特点:

(3)总结

(五)内部类

(1)特点

(2)修改上面那道oj题

(3)总结

(六)匿名对象

(1)使用场景

(2)特点


(一)再探构造函数

(1)初始化列表

        这是每个成员变量初始化的地方,先给个代码看看效果:

class Date
{
public:
Date(int year = 1,int month = 1,int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};

        这里用我们熟悉的Date类举例,Date构造函数我们以前实现过,现在显式写它的初始化列表,即:

:_member1(parameter1)
,_member2(parameter2)
,_member3(parameter3)

注意点:

  • ①2个必须出现在初始化列表的成员变量:引用,const修饰,自定义类型。

  • ②还有1个自定义类型成员变量:如果没有默认构造函数(一共3种,可在类和对象(中)一文(1)特点中回顾),就必须有初始化列表。

        之前我们有提到过,如果自定义类型没有默认函数,编译器会报错:

class Stack
{
public:
Stack(int capacity)
{
// …
}
private:
STDataType* _arr;
size_t _top;
size_t _capacity;
};

class MyQueue
{
public:

private:
Stack pushSt;
Stack popSt;
};

int main()
{
MyQueue q;

return 0;
}

        这种情况下我们除了给Stack类添加默认函数外,还可以显式写MyQueue类的初始化列表。

MyQueue(int capacity)
:pushSt(capacity)
,popSt(capacity)
{}

  • ​③建议统一使用初始化列表初始化,避免未初始化报错情况。

  • ④对象初始化顺序按照声明顺序来,建议 声明顺序 和 初始化列表变量顺序 保持一致。

class Date
{
public:
Date(int month = 1, int year = 1, int day = 1)
:_year(year)
, _day(day)
,_month(month)
{
//…
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;

return 0;
}

        这里Date类构造函数的 参数列表 和 初始化列表 都是跟 声明顺序 不一样的,经过调试,发现 对象d 中 各成员 初始化顺序与声明顺序一致,以下为调试视频:

initializationList

(2)缺省值

        C++支持成员变量在声明的时候给缺省值,这个缺省值会给到初始化列表中对应的变量。

        上文我们写Date类,在成员声明的时候没有给缺省值,现在加上:

class Date
{
public:
Date(int year = 1,int month = 1,int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year = 0;
int _month = 0;
int _day = 0;
};

        以下为调试结果:

        说明相比 声明缺省值,编译器优先取 构造函数参数 的值,具体逻辑图我们往下看。

(3)成员变量初始化逻辑图

(二)类型转换

(1)内置类型 转 自定义类型:=

class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
int main()
{
//原先我们创建对象:A a(1);
A a = 1;

return 0;
}

        main函数中,我创建了一个对象a,其中就涉及 int类型 转 A类型的隐式转换。

注意点

  • 引用直接构造需要加const修饰,因为涉及临时变量问题。

class B
{
public:
B(int b)
:_b(b)
{}
private:
int _b;
};

int main()
{
const B& b = 1;

return 0;
}

(2)内置类型 转 自定义类型:={}

class C
{
public :
C(int c1,int c2)
:_c1(c1)
,_c2(c2)
{}
private:
int _c1;
int _c2;
};
int main()
{
C c = {1,1};

return 0;
}

        调试:

(3)可以用A构造B

        我们重载一个B的构造函数:

B(A& a)
:_b(a.get()){}

        主函数测试:

A a(1);
B b = a;

        调试:

(4)加explicit修饰,不支持隐式类型转换

        我们给A的构造函数加上explicit修饰:

explicit A(int a)
:_a(a){}

        主函数测试:

A a = 1;

        可以发现编译器报错:

(三)static成员

(1)静态成员变量

        给A类加上static成员变量:

class A
{
public:
A(int a)
:_a(a)
{
++_count;
}
private:
int _a;
static int _count;
};
int A::_count = 0;

        这里我加了_count成员变量,用来记录已创建对象数目。

注意点

  • ①静态成员变量一定要在类外初始化,相当于全局变量。具体看到上面的代码最后一行。

  • ②存放在静态区,所有对象共有,求sizeof(className)的时候不算它。

        调试:

  • ③它不走初始化列表,不能声明的时候给默认值。

        我给_count在声明的时候加上了默认值:

class A
{
public:
A(int a)
:_a(a)
{
++_count;
}
private:
int _a;
static int _count = 0;
};

        编译器报错:

(2)静态成员函数

        由于_count受访问限定符的限制,不能直接获取它的值。

cout << A::_count << endl;

        编译器报错:

        这里我加一个getCnt静态函数,用来获取_count变量的值:

static int getCnt()
{
return _count;
}

        调试:

(↑↑↑ 指定类域就可以访问静态函数)

注意点

  • ①静态成员函数不会传this指针,不能访问非静态成员。

        这里我在return前访问非静态成员变量:

static int getCnt()
{
cout << this->_a << endl;
return _count;
}

        编译器报错:

  • ②另外,非静态函数可以访问静态和非静态成员。

void test()
{
cout << this->_a << endl;
cout << _count << endl;
}

        调试:

(3)oj题

        来做一道针对我们当前学的知识点的题目:

        求1+2+3+…+n_牛客题霸_牛客网

class Help {
public:
Help()
{
_sum += _n;
_n++;
}
static int get() {
return _sum;
}
private:
static int _sum;
static int _n;
};
int Help::_n = 1;
int Help::_sum = 0;

        我创建了一个Help类,sum成员变量用来累加题目所求的总和值,n成员变量充当式子中一个一个的加数。

class Solution {
public:
int Sum_Solution(int n) {
Help arr[n];
return Help::get();
}
};

        这里arr[n]用到了变长数组,创建了n个Help对象,实现了_n从1到n的累加。

(四)友元

(1)重载输入输出运算符

ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);

        我们知道重载这个函数需要声明成全局函数(前面类和对象(中)运算符重载特点里),但是我们需要类成员变量的访问权。

        我们就可以把这俩函数在Date类里声明成友元函数:

class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
// …
}

        具体代码看这篇文类和对象:实现Date类默认函数:运算符重载最后一个。

(2)友元

A.友元函数

        在类中加一个友元声明,函数就可以访问private成员变量了。

        文件Date.h中(错误写法!!!):

class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
// …
}
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << '/' << d._month << '/' << d._day << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
cout << "please enter the year,month,and day in order:";
in >> d._year >> d._month >> d._day;

assert(d._month > 0 && d._month < 13);
assert(d._day > 0 && d._day <= d.getMonthDays(d._year, d._month));

return in;
}

        这种写法拿去TestDate.cpp文件测试使用,会重定义报错。

        因此我们把它改为内联函数,使它只有一份。

inline ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << '/' << d._month << '/' << d._day << endl;
return out;
}
inline istream& operator>>(istream& in, Date& d)
{
cout << "please enter the year,month,and day in order:";
in >> d._year >> d._month >> d._day;

assert(d._month > 0 && d._month < 13);
assert(d._day > 0 && d._day <= d.getMonthDays(d._year, d._month));

return in;
}

        或者把声明和定义分离。

//声明在Date.h中
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);

//定义在Date.cpp中
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << '/' << d._month << '/' << d._day << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
cout << "please enter the year,month,and day in order:";
in >> d._year >> d._month >> d._day;

assert(d._month > 0 && d._month < 13);
assert(d._day > 0 && d._day <= d.getMonthDays(d._year, d._month));

return in;
}

        这个就是类和对象:实现Date类一文里的实现方式。

特点:
  • ①外部友元函数可以访问类的所有成员变量(包括private成员变量)。

  • ②友元函数是外部的函数,不算作类的成员函数。

  • ③友元函数不受访问限定符的限制。我们实现Date类就是把它放在了类开头。

  • ④一个外部函数可以成为多个类的友元函数。

        我们在这里假设一个类里的函数要成为两个类的友元函数:

class B;
class C;

        先得给后两个类声明一下,避免编译器找不到。

        class A:

class A {
public:
static void a(const B& b, const C& c);
};

        class B:

class B {
friend void A::a(const B& b, const C& c);
public:
B(int b = 2)
:_b(b) {}
private:
int _b;
};

        B类有一个private成员变量_b。                

        class C:

class C {
friend void A::a(const B& b, const C& c);
public:
C(int c = 3)
:_c(c) {}
private:
int _c;
};

        C类有一个private成员变量_c。

        定义A::a:

void A::a(const B& b, const C& c) {
cout << "b=" << b._b << ",c=" << c._c << endl;
}

        输出B类对象和C类对象的值。

B b;
C c;
A::a(b,c);

        运行结果:

B.友元类

        上例子:B是A的友元类

        class A:

class A{
friend class B;
public:
A(int a = 1)
:_a(a){}
private:
int _a;
};

        A类有一个private成员变量_a。

        class B:

class B{
public:
B(const A& a)
:_b(a._a){}
private:
int _b;
};

        B类也有一个private成员变量_b。

A a;
B b(a);

        B类可以直接访问A类的_a变量,所以可实现b._b根据_a的值初始化:

 特点:
  • ①友元类里的成员函数都是另一个类的友元函数。

  • ②友元类的关系是单向的,B是A的友元类,但是不代表A是B的友元类。

        我们在A类中加个测试函数:

void test(const B& b) {
cout << b._b << endl;
}

        编译器立马报错了:

  • ③友元类的关系不能传递,B是A的友元类,C是B的友元类,不代表C是A的友元类。

        我们写一个C类,C类为B的友元类:

class C{
public:
void test(const A& a) {
cout << a._a << endl;
}
};

        编译器报错,C类不是A的友元类,无法访问_a:

(3)总结

        友元提供了一种突破类访问限定符封装的方式,但是这会增加耦合度,不宜多用。

(五)内部类

        先写一个内部类看看效果:

class A{
public:
A(int a = 1)
:_a(a){}
class B {
public:
static void test(const A& a){
cout << a._a << endl;
}
};
private:
int _a;
};

        这里B是A的内部类,所以可以直接访问_a。

A a;
A::B::test(a);

        运行结果:

(1)特点

  • ①内部类只受 外部类类域 和 访问限定符 限制。

        把内部类B放到private下,外部就访问不到了:

class A{
public:
A(int a = 1)
:_a(a){}
private:
class B {
public:
static void test(const A& a) {
cout << a._a << endl;
}
};
int _a;
};

        测试代码还跟刚刚一样:

A a;
A::B::test(a);

        但是编译器报错了:

  • ②内部类默认就是类的友元类。

  • ③求类大小的时候,不算内部类里的成员变量。

cout << sizeof(A) << endl;
cout << sizeof(A::B) << endl;

        运行结果:

(2)修改上面那道oj题

        求1+2+3+…+n_牛客题霸_牛客网

class Solution {
public:
class Help {
public:
Help() {
_sum += _n;
_n++;
}
static int get() {
return _sum;
}
private:
static int _sum;
static int _n;
};
int Sum_Solution(int n) {
Help arr[n];
return Help::get();
}
};
int Solution::Help::_n = 1;
int Solution::Help::_sum = 0;

        Help类可以作为Solution类的内部类。

(3)总结

        内部类就好像把友元类封装到了一个类里面。

(六)匿名对象

        没有名字,直接用类创建对象:

class A{
public:
A()
{
cout << "constructor" << endl;
}
};

int main(){
A();

return 0;
}

        运行结果:

(1)使用场景

        只想使用一下类里的一个函数就可以使用匿名对象,这里我只是想用一下A类里的test函数:

class A{
public:
A()
{
cout << "constructor" << endl;
}
void test() {
cout << "test" << endl;
}
};
int main(){
A().test();

return 0;
}

        运行结果:

(2)特点

  • ①这个匿名对象的生命周期随这一语句结束而结束:

        给A类加上析构函数方便观察:

~A()
{
cout << "destory" << endl;
}

        测试代码:

A().test();
cout << "end" << endl;

        运行结果:

        可以看到在end输出之前,这个匿名对象就已经完成析构了。

  • ②const可延长生命周期。 

        测试代码:

const A& a = A();
a.test();
cout << "end" << endl;

        编译器报错:        

        需要改一下A::test函数:

void test() const {
cout << "test" << endl;
}

        运行结果:

        可以观察到这个匿名对象在end输出后才析构,生命周期延长到了进程结束之前。

赞(0)
未经允许不得转载:171主机测评 » 类和对象(下)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址