【类和对象(完结)】(一)

简介: 【类和对象(完结)】(一)

1. 再谈构造函数

1.1 构造函数体赋值

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 初始化列表

初始化列表:以一个 冒号开始 ,接着是一个以 逗号分隔的数据成员列表 ,每个 " 成员变量 " 后面跟一个 放在括号中的初始值或表达式。

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=0)
  {
    _a = a;
  }
private:
  int _a;
};
class Date
{
public:
  Date(int year, int month, int day)
  {
    _N = 10;
    _year = year;
    _month = month;
    _day = day;
  }
private:
  int _year;  // 声明
  int _month;
  int _day;
  const int _N;  // const
  int& _ref;     // 引用
  A _aa;         // 没有默认构造函数的自定义类型成员变量
};

990638f7db214e5a8abefcbd86cee21e.png


当出现了上面的情况时用初始化列表就能够解决:

class A
{
public:
  A(int a)
  {
    _a = a;
  }
private:
  int _a;
};
class Date
{
public:
  Date(int year, int month, int day, int i)
    :_N(10)
    , _ref(i)
    , _aa(-1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
private:
  int _year;  // 声明
  int _month;
  int _day;
  const int _N;  // const
  int& _ref;     // 引用
  A _aa;  // 没有默认构造函数的自定义类型成员变量
};

不知道大家注意到没有,当我们使用初始化列表时,自定义类型会自动调用它的默认构造函数,可是它的默认构造函数是没有的,如果不按照初始化列表这样初始化,那编译器是会报错的,这样初始化就可以不用全缺省。

大家再来看这样一道题:

//第一种:
class Date
{
public:
    Date(const int* year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
   const int* _year;
    int _month;
    int _day;
};
//第二种:
class Date
{
public:
    Date(int* const year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
   int* const _year;
    int _month;
    int _day;
};

哪一种会报错?

bdbffec2ed6a411793cc2997d2930db4.png 采用这种方式修饰的话year本身就不可以改变,就只能够采用初始化列表。

//第二种:
class Date
{
public:
  Date(int* const year, int month, int day)
    :_year(year)
    ,_month(month)
    ,_day(day)
  {}
private:
  int* const _year;
  int _month;
  int _day;
};

3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化。

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 吗?再想想,会有这么简单吗,再思考一下,提示一下:

初始化列表中的初始化顺序就是成员变量在类中声明次序。

那么我想你已经有了结果,既然类中初始化顺序是先_a2,后_a1,所以在初始化列表是先执行_a2(_a1),再_a1(a),所以_a2被初始化成了随机数,_a1就被初始化成了a( 1 ),我们可以自己运行起来结果:43e4c7b121d54b95ba3bb2984f087f75.png

1.3 explicit关键字

构造函数不仅可以构造与初始化对象, 对于单个参数或者除第一个参数无默认值其余均有默认值 的构造函数,还具有类型转换的作用 

class Date
{
public:
   Date(int year=1, 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);
  d1 = 2023;
}

用一个整形变量给日期类型对象赋值,实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值,但是这样的可读性肯定太糟糕了,有什么处理办法吗?

这个时候就要用explicit关键字了,只要我们在构造函数前加一个explicit关键字,那么上面这种用法编译器会直接报错。

2. static成员

2.1 概念

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

面试题:实现一个类,计算程序中创建出了多少个类对象。

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;
}
int main()
{
  TestA();
  return 0;
}

2.2 特性

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

2. 静态成员变量 必须在 类外定义 ,定义时不添加 static 关键字,类中只是声明 。

3. 类静态成员即可用 类名 :: 静态成员 或者 对象 . 静态成员 来访问

4. 静态成员函数 没有 隐藏的 this 指针 ,不能访问任何非静态成员

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

【问题】

1. 静态成员函数可以调用非静态成员函数吗?

2. 非静态成员函数可以调用类的静态成员函数吗?

回答: 静态成员函数不可以调用非静态成员函数,由于静态成员函数没有this指针。非静态成员函数可以调用类的静态成员函数,可以用this指针调用。

3. 友元

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

友元分为:友元函数友元类

3.1 友元函数

问题:现在尝试去重载 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 << endl;
    return _cout;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1(2022, 12, 26);
  d1 << cout;
  return 0;
}

这里将operator<<重载成成员函数,但是用法却是不符合我们的常识的:以前我们用cout,是直接cout<

将operator<<重载成全局函数可行吗?如果将operator<<重载成全局函数,但是类外又没有办法访问成员,按照我们之前的学习可以用的方法有:自己实现在类中GetYear,GetMonth,GetDay;或者将成员变量的限定符改为public.但是这样用第一种方式处理就太麻烦了,第二种方式处理类的封装性又遭到了破坏,这里就可以用友元函数来处理了。不是说友元函数也会破坏封装性吗?话虽如此,但是用友元函数会比直接修改成员变量的限定符要好些。

友元函数可以直接访问类的私有成员,它是定义在类外部普通函数,不属于任何类,但需要在类的内部声明,声明时需要加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;
}

说明:

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


Fox!
+关注
目录
打赏
0
0
0
0
2
分享
相关文章
【C++】类和对象(下篇)
【C++】类和对象(下篇)
44 0
|
5月前
|
C++之类与对象(完结撒花篇)(上)
C++之类与对象(完结撒花篇)(上)
55 0
C++之类与对象(完结撒花篇)(下)
C++之类与对象(完结撒花篇)(下)
46 0
【类和对象(完结)】(二)
【类和对象(完结)】(二)
82 0
|
10月前
|
类和对象(下篇)
类和对象(下篇)
73 1
类和对象(上篇)
类和对象(上篇)
60 1
【C++】—— 类和对象(中)一张图带你搞清楚6个默认成员函数+万字总结 复习全靠它(3)
【C++】—— 类和对象(中)一张图带你搞清楚6个默认成员函数+万字总结 复习全靠它(3)
96 0
【C++】—— 类和对象(中)一张图带你搞清楚6个默认成员函数+万字总结 复习全靠它(3)
【C++】—— 类和对象(中)一张图带你搞清楚6个默认成员函数+万字总结 复习全靠它(2)
【C++】—— 类和对象(中)一张图带你搞清楚6个默认成员函数+万字总结 复习全靠它(2)
107 0
【C++】—— 类和对象(中)一张图带你搞清楚6个默认成员函数+万字总结 复习全靠它(2)
【C++】—— 类和对象(中)一张图带你搞清楚6个默认成员函数+万字总结 复习全靠它(1)
【C++】—— 类和对象(中)一张图带你搞清楚6个默认成员函数+万字总结 复习全靠它(1)
124 0
【C++】—— 类和对象(中)一张图带你搞清楚6个默认成员函数+万字总结 复习全靠它(1)