C++类与对象(下)

目录

1.再谈构造函数

2.static成员

3.友元

3.1友元函数

3.2友元类

4.内部类

5.拷贝对象时的一些编译器优化

6.再了解类与对象


1.再谈构造函数

class Date
{
public:
Date(int year, int month, int day)
// {
 //   //在函数体内进行初始化
 //    _year = year;
//     _month = month;
//     _day = day;
// }

Date(int year,int month,int day)
     :_year(year)
     ,_month(month)
     ,_day(day)
     ,_ref(year)
     ,_n(1)
 {    
//初始化列表初始化
 }
private:
int _year;
int _month;
int _day;
int&_ref;
const int _n;
};

原来的初始化方法不可以吗,为什么还有一种新的初始化的方法?

没错,在函数体内初始化的时候,引用和const修饰的成员是无法在函数体内进行初始化的,必须通过初始化列表来进行初始化。

首先我们需要知道,在类的私有中,是对成员进行声明,那么这些成员在哪里定义呢,很明显,可以在函数体内或者初始化列表中,但是我们知道const成员和引用的成员必须在定义的时候初始化,这样在函数体内初始化是行不通的,所以c++选择了使用初始化列表.

并且初始化列表和函数体内初始化可以混合着来使用:

Date(int year,int month,int day)
:_ref(year)
,_n(1)
{
_year=year;
_month=month;
_day=day;
}

 剩下三个成员没有在初始化列表中显示写出来定义,但是他们也会定义,只是内置类型会初始化成随机值,如果是自定义类型的成员,那么就会去调用他的默认构造函数。

初始化列表解决的问题:

1.必须在定义的地方显示初始化   例如 引用,const成员以及没有默认构造的自定义成员

2.有些自定义成员想要显示初始化,自己来控制初始化的内容

结论:尽量去使用初始化列表。

那么初始化列表这么方便,能不能只使用初始化列表,不要函数体初始化呢,答案是不能:

class Stack
{
Stack(int n=2)
:_a((int *)malloc(sizeof(int)*n))
,_top(0)
,_capacity(n)
{

}
int* _a;
int _top;
int _capacity;
}

 这样使用初始化列表,万一malloc函数失败了,这么无法反馈,在这里使用函数体初始化比较好,初始化,主体用初始化列表。

初始化列表初始化的顺序是按照声明的顺序来的,我们建议声明和初始化列表的顺序一致

2.static成员

A aa;//有名对象

 A() //匿名对象生命周期只在这一行

static int GetCount();这是一个静态成员函数,特点是没有this指针

class A
{
public:
A() { ++_scount; }
A(const A& t) { ++_scount; }
~A() { --_scount; }
static int GetACount() { return _scount; }
private:
static int _scount;
};
int A::_scount = 0;
void TestA()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
}

 我们可以看到有静态函数 static int GetACount();在调用这个函数的时候,我们发现直接使用类域A::就可以调用这个函数,而不是需要创建一个对象或者使用匿名对象来调用这个函数,这是因为静态函数没有this指针,所以在调用的时候不需要用一个对象来调用,直接使用类域调用即可

静态成员函数不能访问非静态成员函数和非静态成员变量,因为静态成员函数没有this指针

 静态成员函数和静态成员变量,本质是限制的全局变量和全局函数,只是他专属这个类,受类域和访问限定符的限制

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化

1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

3.友元

3.1友元函数

友元提供了一种突破封装的方式,有时提供了便利,但是友元会增加耦合度,所以友元不宜多用

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

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;
}

 上面是上一篇博客中遇到的问题,输入输出流现在类中会传一个隐藏的this指针,写在类外这个问题就会解决,但是与此同时类外无法访问到类中的私有成员,所以我们就使用了友元函数。

友元函数可访问类的私有和保护成员,但不是类的成员函数
友元函数不能用const修饰
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
一个函数可以是多个类的友元函数
友元函数的调用与普通函数的调用原理相同

而且友元函数是单向的

3.2友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元。

4.内部类

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
3. sizeof(外部类)=外部类,和内部类没有任何关系。

 内部的类天生就是外部的类的友元函数,例如下面B天生就是A的友元函数

class A
{
private:
 static int k;
 int h;
public:
 class B 
 {
 public:
 void foo(const A& a)
 {
 cout << k << endl;//OK
 cout << a.h << endl;//OK
 }
 };
};
int A::k = 1;
int main()
{
    A::B b;
    b.foo(A());
    
    return 0;
}

 并且我们求类A的大小sizeof(A);运算出来的结果是4;没有对B进行实例化。

但是C++中内部类的使用比较少

5.拷贝对象时的一些编译器优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的。

class A
{
public:
 A(int a = 0)
 :_a(a)
 {
 cout << "A(int a)" << endl;
 }
 A(const A& aa)
 :_a(aa._a)
 {
 cout << "A(const A& aa)" << endl;
 }
 A& operator=(const A& aa)
 {
 cout << "A& operator=(const A& aa)" << endl;
 if (this != &aa)
 {
 _a = aa._a;
 }
 return *this;
 }
 ~A()
 {
 cout << "~A()" << endl;
 }
private:
 int _a;
};
void f1(A aa)
{}
A f2()
{
 A aa;
 return aa;
}
int main()
{
 // 传值传参
 A aa1;
 f1(aa1);
 cout << endl;
 // 传值返回
 f2();
 cout << endl;
 // 隐式类型,连续构造+拷贝构造->优化为直接构造
 f1(1);
 // 一个表达式中,连续构造+拷贝构造->优化为一个构造
 f1(A(2));
 cout << endl;
 // 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造 A aa2 = f2();
 cout << endl;
 // 一个表达式中,连续拷贝构造+赋值重载->无法优化
 aa1 = f2();
 cout << endl;
 return 0;
}

我们需要知道的是,在不同的编译器的下优化不同。

6.再了解类与对象

现实生活中的实体计算机并不认识,计算机只认识二进制格式的数据。如果想要让计算机认识现实生活中的实体,用户必须通过某种面向对象的语言,对实体进行描述,然后通过编写程序,创建对象后计算机才可以认识。比如想要让计算机认识洗衣机,就需要:
1. 用户先要对现实中洗衣机实体进行抽象---即在人为思想层面对洗衣机进行认识,洗衣机有什
么属性,有那些功能,即对洗衣机进行抽象认知的一个过程
2. 经过1之后,在人的头脑中已经对洗衣机有了一个清醒的认识,只不过此时计算机还不清
楚,想要让计算机识别人想象中的洗衣机,就需要人通过某种面相对象的语言(比如:C++、Java、Python等)将洗衣机用类来进行描述,并输入到计算机中
3. 经过2之后,在计算机中就有了一个洗衣机类,但是洗衣机类只是站在计算机的角度对洗衣
机对象进行描述的,通过洗衣机类,可以实例化出一个个具体的洗衣机对象,此时计算机才能洗衣机是什么东西。
4. 用户就可以借助计算机中洗衣机对象,来模拟现实中的洗衣机实体了。
在类和对象阶段,大家一定要体会到,类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象。

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值