欢迎光临
我们一直在努力

C++类与对象完整学习路径下(下)

目录

前序

一、初始化列表

这到底是初始化列表还是构造函数?

初始化列表存在的必要性

初始化列表的功能只有这样吗?

私有成员对象的隐形坑

总结

二、类型转换

三.static成员

1.为什么说静态成员变量不属于某个具体对象?

2.为什么说静态成员变量为所有类对象共享,不属于某个具体的对象?

四、友元与内部类

1.友元函数

2.友元类

形象比喻:

3.内部类


前序

        大家好,我是小沐。前面的文章里面我们已经共同学习了默认成成员函数中的构造函数,但是构造函数的知识还有一部分等着我们去挖掘,也就是本篇的核心内容——初始化列表,当然还有其它补充内容。

一、初始化列表

        以前我们初始化成员变量主要是在函数体内赋值,但是构造函数初始化还有另外一种方式——初始化列表。

理清初始化列表和构造函数的关系:初始化列表和构造函数是一体的。

        我们一起来认识

#include <iostream>
using namespace std;
class A
{
public:
A(int n = 2)
:_a(n)
, _b(_a)
{
cout <<"_a:" << _a << endl;
cout <<"_b:" << _b << endl;
}
private:
int _a;
int _b;
};

int main()
{
A d;
return 0;
}

        看到这,很多小白可能就疑惑了,这和构造函数有什么区别呢?

答案是没有区别,以前我们没有显式写初始化列表时,编译器是自动帮我们生成了默认列表,就和自动生成默认成员函数一样。

这到底是初始化列表还是构造函数?

        这当然是构造函数,初始化列表是构造函数的一部分。 :号后面,{}前面的这一部分就是初始化列表。以前我们在函数体内初始化叫做函数体内赋值。

        注:如果形参定义了却没有定义缺省值,就不是构造函数,而是一个纯正的初始化列表

:_a(n)
, _b(_a)

这一部分叫做初始化列表

A(int n = 2)
:_a(n)
, _b(_a)
{
cout <<"_a:" << _a << endl;
cout <<"_b:" << _b << endl;
}

        而整体叫做构造函数。

这时好学的你肯定想问:在函数体内赋值就可以完成的工作,增加一个初始化列表不就是纯多余吗?

初始化列表存在的必要性

        以前我们都学过const修饰变量,那让我们来看这个例子

const a;
const& b;
a = 1;
b = 2;

        这种写法是不是有着很大的问题,我们都知道const修饰常量,被const修饰的变量和const引用的变量必须在定义的时候就初始化。可如果是成员变量呢?

class A
{
public:
A(int n = 2)
:_a(n)
, _b(_a)
{
cout <<"_a:" << _a << endl;
cout <<"_b:" << _b << endl;
}
private:
int _a;
int _b;
const int& _c;
};

        这样的你又怎么给他赋值呢?有的同学可能说,我直接在定义的时候给它一个初始值就行了嘛,像这样

class A
{
public:
A(int n = 2)
:_a(n)
, _b(_a)

{
cout <<"_a:" << _a << endl;
cout <<"_b:" << _b << endl;
}
private:
int _a;
int _b;
const int& _c = 1;
};

        那么请问,如果我这一次使用程序想让_c是另一个经常使用的重要常数,怎么办?你这种写法就已经在底层给它写死,不就达不到自己的预期了吗?这时候初始化列表的作用就体现出来了。

class A
{
public:
A(int a = 2,int b = 2, int c)
:_a(a)
, _b(b)
,_c(c)
{
cout <<"_a:" << _a << endl;
cout <<"_b:" << _b << endl;
}
private:
int _a;
int _b;
const int& _c = 1;
};

                这样就可以根据需要来定义常量值了,const 变量和const引用的定义初始化问题就解决了

初始化列表的功能只有这样吗?

        当然不止于此,初始化列表还可以使用于表达式和没有默认构造的类类型变量,如

class Time
{
public:
Time(int hour)
:_time(hour)
{
cout << "_time:" << _time << endl;
}
private:
int _time;
};
class Date
{
public:
Date(int a = 2,int b = 2, int c = 2)
:_a(a)
, _b(b)
,_c(c)
,_T(1) //可以是类类型变量

,_ptr((int*)malloc(sizeof(int)*4)) //括号里面可以是表达式
{
if (_ptr == nullptr)
{
perror("_ptr::malloc");
return;
}
cout <<"_a:" << _a << endl;
cout << "_b:" << _b << endl;
cout <<"_c:" << _c << endl;
}
~Date()
{
free(_ptr);
_ptr = nullptr;
}
private:
int _a;
int _b;
const int& _c;
Time _T;
int* _ptr;
};

int main()
{
Date d;

return 0;
}

        很明显,这样就大大提高初始化列表的使用灵活性。当然这里还有一种操作,我们先来看问题。

私有成员对象的隐形坑

下面程序的运行结果结果是什么?

A. 输出 1 1

B. 输出 2 2

C. 编译报错

D. 输出1 随机值

E. 输出 1 2

F. 输出 2 1

class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{ }
void Print()
{
cout << _a1 << ' ' << _a2 << endl;
}
private:
int _a2 = 2;
int _a1 = 2;
};

int main()
{
A aa(1);
aa.Print();
return 0;
}

我们来看编译结果

在回答为什么之前,你先问自己,下面这段代码它是初始化吗?

private:
int _a2 = 2;
int _a1 = 2;

        在私有域里面这种叫做声明,没有初始化,等号后面的值表示缺省值,在编译这里时系统并没有给它开空间。

        回到前面的问题,为什么输出值不是1 1

在初始化列表中,初始化的顺序不是按照列表里面的代码顺序来逐个初始化的,而是按照在private中声明的先后顺序来进行初始化的。原因是物理内存中是按声明顺序进行存储的。

        所以,在进入初始化列表后,编译器会先对_a2进行初始化,而_a2是用_a1的值初始化的,这个时候的_a1是随机值,导致赋值给_a2后也是随机值。

这里如果理解模糊的同学也可能会问

按照这个规则,_a1不是有默认值吗?应该会用默认值赋值给_a2才对啊,怎么会是随机值呢?

        因为_a1在初始化列表里面是用了n进行初始化的,而当选择了用n进行初始化后,_a1的缺省值也就作废了,编译器已经不认它了,只认n,导致没有初始化到_a1时它就是一个随机值。

总结

二、类型转换

        C++支持内置类型隐式转换为类类型对象,但需要有相关内置类型为参数的构造函数。

class A
{
public:
// 构造函数explicit就不再⽀持隐式类型转换
// explicit A(int a1)
A(int a1)
:_a1(a1)
{
}
//explicit A(int a1, int a2)
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{
}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}

int Get() const
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
B(const A& a)
:_b(a.Get())
{
}
private:
int _b = 0;
};
int main()
{
// 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa3
// 编译器遇到连续构造+拷⻉构造->优化为直接构造
A aa1 = 1;
aa1.Print();
const A& aa2 = 1;
// C++11之后才⽀持多参数转化
A aa3 = { 2,2 };
// aa3隐式类型转换为b对象
// 原理跟上⾯类似
B b = aa3;
const B& rb = aa3;
return 0;
}

        可以看到,C++是可以让类类型像C语言中不同类型之间隐式转换一样,不同于C语言的是,C++发生隐式类型转换会创建临时变量,创建临时变量就会调用拷贝构造,再将临时变量返回赋值,不过现在的编译器已经把这个过程进行优化,会直接构造。

三.static成员

        用static修饰的成员变量就叫做静态成员变量,修饰成员函数就叫做静态成员函数。

static成员特点:

        1.静态成员变量为所有类对象共享,不属于某个具体的对象,不存在对象中。

        2.静态成员函数没有this指针。

        3.静态成员函数可以访问其它的静态成员,但由于没有this指针导致不能访问非静态的成员。

        4.非静态成员函数可以访问任意静态成员和静态成员函数。

        5.静态成员也是类的成员,受public、protect和private访问限定符的限制。

        6.静态成员变量不能在声明位置给缺省值,因为缺省值是构造函数初始化列表的,静态成员函数不属于某个对象,不走构造函数初始化列表。

1.为什么说静态成员变量不属于某个具体对象?

        思考一下,类A的大小是多少

class A
{
public:
void Print()
{
cout << sizeof(A) << endl;
}
private:
static int _a;
static int _b;
};

int main()
{
A aa;
aa.Print();
return 0;
}

公布答案

        这里的1个字节是占位符的大小,表示类A存在,所以说static成员变量是不存在某一个具体对象里面的。

2.为什么说静态成员变量为所有类对象共享,不属于某个具体的对象?

class A
{
public:
A()
{
_A++;
}
~A()
{
_A–;
}
void Print()
{
cout << sizeof(A) << endl;
}
int GetStaticA()
{
return _A;
}

private:
static int _A;

};
int A::_A = 1;

int main()
{
A a1;
A a2;
cout << a1.GetStaticA() << endl;
cout << a2.GetStaticA() << endl;
return 0;
}

        我们把_A放在构造函数里面,如果_A属于某个具体的对象,那么a1和a2输出的结果应该都是2,如果是共享的,那么a1初始化时_A加1,a2初始化时又加1,输出结果应该都是3  

        所以说静态的成员函数是同类类型的对象共享的,并不属于某一个具体的对象。又因为它是属于A类,现定义另一个类B,B是不能访问_A,说明静态成员函数是受访问限定符限制的。其它的特点在刚刚的代码里面也已经展现出来了的。

四、友元与内部类

        我们都知道,两个类之间是不能直接访问彼此的私有成员变量的,友元就给我们提供了一种突破访问限定符封装的方式。

友元:1.友元函数

           2.友元类

1.友元函数

class B;
class A
{
public:
friend void func(const A& aa, const B& bb);
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
friend void func(const A& aa, const B& bb);
private:
int _b1 = 3;
int _b2 = 4;
};

void func(const A& aa, const B& bb)
{
cout << aa._a1 << endl;
cout << bb._b1 << endl;
}

int main()
{
A aa;
B bb;
func(aa, bb);
return 0;
}

        在之前,我们类外面是不能直接访问私有的成员变量的,但是现在我把func放到A和B里面,用friend进行修饰,就相当于在A和B的朋友名册里面加上了一条:func是我的好朋友;那好朋友拿自己的成员变量看一下,只是允许的。

2.友元类

        友元类的原理和友元函数一样,把B方到A里面用friend修饰,A就会认为B是A的好朋友,允许B访问A的私有成员变量。

class B;
class A
{
public:
friend class B;
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
void func(const A& aa, const B& bb)
{
cout << aa._a1 << endl;
cout << bb._b1 << endl;
}
private:
int _b1 = 3;
int _b2 = 4;
};

int main()
{
A aa;
B bb;
bb.func(aa, bb);
return 0;
}

        这样B就能直接访问A的私有成员变量了,但是这里A能不能访问A的成员变量呢?

        显然A是不能直接访问B的私有成员变量的。

形象比喻:

        定义类A和类B,并且在A中用friend修饰B。A男同学暗恋B女同学,默默把她放在心中,但是B同学不知道,当B同学想找A同学借一些私人性质比较强的东西时,A大概率会借;而反过来,A同学找B同学借的话,B会比较排斥。

3.内部类

        如果一个类定义在另一个类的内部,这个内部的类就叫做内部类。虽然内部类是定义类外部类的里面,但是它却和成员函数类似,也是一个独立的类,在计算外部类的大小时并不包含在里面。

class A
{
public:

class B
{
public:
private:
int _b1 = 1;
};
void Print()
{
cout << sizeof(A) << endl;
}

private:
};

int main()
{
A aa;
aa.Print();
return 0;
}

        可以看到,输出结果仅有占位符的大小,说名内部类确实是独立与外部类的,外部内定义对象中是不包含内部类的。

        但是这里又有个特别有意思的事情,外部类会把内部类当友元类,而内部类却没有把外部类当作友元,和上一节的友元类如出一辙,我们就不再解释。

内部类本质也是一种封装,A类跟B类紧密关联,A类实现出来主要就是给B类使用的。


        

赞(0)
未经允许不得转载:171主机测评 » C++类与对象完整学习路径下(下)
分享到: 更多 (0)

评论 抢沙发

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