嗨~大家好,这里是春栀怡铃声的博客~

“做你害怕的事,然后发现,不过如此~”

书接上回,我们继续学习类和对象剩余内容~

目录

初始化列表

类型转换

static成员

友元

内部类

匿名对象


初始化列表

形式:

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

初始化列表的内容,特别注意第一个形式是加冒号,后面的形式是加逗号,并且每一句末尾都没有分号

2.每个成员变量只能在初始化列表中初始一次,换句话说是在初始化列表中定义

3.引用类型,const 修饰的,没有默认构造的自定义类型变量  必须在初始化列表里定义

默认构造指的是会自动调用

例子1.全缺省的构造函数会自动调用

还有一种情况 不是缺省构造函数 但是在初始化列表中定义了的,就会自动调用了

所以为了避免出现自定义类型编译器不认识的情况,必须要在初始化列表中定义自定义类型变量

C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的

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

不过,尽量在初始化列表中初始化,在声明中有缺省值的也会走初始化列表,而且初始化列表用的也是声明当中的缺省值,如果你没有给缺省值,对于没有显示在初始化列表 初始化 的内置类型成员 是否初始化 取决于编译器,C++并没有规定。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。

请看一段代码示例:

#include<iostream>
using namespace std;
class Time
{
public:
        Time(int hour) //①构造函数  这个不是默认构造 因为不是缺省不会自动调用  
                :_hour(hour)
        {
                cout << "Time()" << endl;
        }
private:
        int _hour;
};
class Date
{
public:
        Date(int& x, int year = 1, int month = 1, int day = 1) //②构造函数
                :_year(year)
                , _month(month)
                , _day(day)
                , _t(12) //初始化列表中定义了,就会调用上面的 ①构造函数
                , _ref(x)// i 对应着 X ,_ref 被绑定到变量 i
                , _n(1)
        {}
        void Print() const
        {
                cout << _year << "-" << _month << "-" << _day << endl;
        }
private:
        int _year;
        int _month;
        int _day;
        Time _t; // 没有默认构造 
        int& _ref; // 引⽤ 
        const int _n; // const 
};
int main()
{
        int i = 0;
        Date d1(i);  //创建 Date d1(i) 时,会调用 Date 的构造函数
        d1.Print();  // i 对应着 X ,_ref 被绑定到变量 i
        return 0;
}

最后这段代码输出结果是  1-1-1

Time() 这个是由 Time 构造函数输出的

Date 构造函数中_year _month _day 使用的是默认值 所以输出是3个1

来看几个特殊类型:

一、声明的地方有缺省值:

public:
Date(int year = 1, int month = 1, int day = 1)
        :_year(year)
        , _month(month)
        //这里没有写_day的初始化,但也会给_day 赋值,
        //如果像这样有缺省值的直接使用,没有的话是随机值
{}
private:
        int _year;
        int _month;
        int _day=13;//有缺省值
};

二、自定义类型没有初始化

1.编译器报错

对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。

class Time
{
public:
        Time(int hour)   //此时不为默认构造函数
                :_hour(hour)
        {
                cout << "Time()" << endl;
        }
private:
        int _hour;
};
class Date
{
public:
        Date(int year = 1, int month = 1, int day = 1)
                :_year(year)
                , _month(month)
                , _day(day)
                //, _t(12)   并且也没有初始化列表
private:
        int _year;
        int _month;
        int _day;
        Time _t; // 没有默认构造 
};

2.可以调用默认构造

对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数

class Time
{
public:
        Time(int hour=10) //此时为默认构造函数
                :_hour(hour)
        {
                cout << "Time()" << endl;
        }
private:
        int _hour;
};
class Date
{
public:
        Date(int year = 1, int month = 1, int day = 1)
                :_year(year)
                , _month(month)
                , _day(day)
                //, _t(12)   没有初始化列表
private:
        int _year;
        int _month;
        int _day;
        Time _t; // 有默认构造 
};

总结:

1.在初始化列表初始化的成员

2.不在初始化列表初始化成员

   a.声明的地方有缺省值用缺省值

   b.没有缺省值

      x.内置类型  编译器决定,大多数是随机值   

      y.自定义类型   有默认构造,调用默认构造     没有默认构造报错

类型转换


C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
构造函数前面加explicit就不再支持隐式类型转换。
类类型的对象之间也可以隐式转换,需要相应的构造函数支持。

请看隐式类型转换代码示意:

#include<iostream>
using namespace std;
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;
 }

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的临时对象,再⽤这个临时对象拷⻉构造aa1
 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;
}

static成员


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

  #include <iostream>
  using namespace std;

  class Student {
  public:
      string name;
      static int count;   // 静态成员变量

      Student(string n) : name(n) {
          count++;
      }
  };

  int Student::count = 0;   // 类外定义

  int main() {
      Student s1("Tom");
      Student s2("Alice");

      cout << Student::count << endl;  // 2
  }


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

   count 不属于 s1 或 s2 某一个对象,而是整个 Student 类共享一份。

所以:

  - 每创建一个对象,count 都可以更新
  - 所有对象看到的是同一个 count

访问静态成员变量

1. 用类名访问

  Student::count

2.通过对象来访问

s1.count

虽然通常也能写,但不推荐,因为容易让人误以为它属于对象。

静态成员函数没有this指针

#include <iostream>
  using namespace std;

  class Student {
  public:
      static int count;

      static void showCount() 
      {
          cout << count << endl;
      }
  };

  int Student::count = 0;

  int main() {
      Student::count = 5;
      Student::showCount();   
  }

  静态成员函数也是属于类,不是属于某个对象。

  所以可以直接这样调用:

 Student::showCount();

 不需要先创建对象。

• 静态成员函数中可以访问其他的静态成员,但是不能访问 非静态的,因为没有this指针。

  class A {
  public:
      int x;
      static void f() {
          cout << x;   // 错误
      }
  };

  //因为 x 是对象自己的数据,而静态函数不绑定任何具体对象,它不知道该访问哪个对象的 x。


• 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。


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

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

友元

友元包括 友元函数 和 友元类

友元函数:

请先看一段代码示意

#include<iostream>
using namespace std;
// 前置声明,防止A的友元函数声明编译器 不认识 B 这个自定义类
class B;

class A
{
  // 友元声明 
  friend void func(const A& aa, const B& bb);
private:
 int _a1 = 1;
 int _a2 = 2;
};
class B
{
 // 友元声明 
 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,并且把友元声明放到⼀个类的里面

友元函数可以在类定义的任何地方声明,不受类访问限定符限制。我们习惯写在类的最前方

外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数

友元类

  #include <iostream>
  using namespace std;

  class B;  // 前置声明

  class A {
  private:
      int x = 100;

      friend class B;   // 声明 B 是 A 的友元类
  };

  class B {
  public:
      void show(A a) {
          cout << a.x << endl;   // 可以访问 A 的 private 成员
      }
  };

  int main() {
      A a;
      B b;
      b.show(a);
  }

在这个代码中,A的友元类是B,代表B可以访问A的私有成员

注意:友元关系不能传递

如果:

  - B 是 A 的友元
  - C 是 B 的友元

  这不代表 C 也是 A 的友元。

 友元类会破坏封装  类的 private 本来是为了隐藏实现细节。
  一旦用了友元类,相当于给另一个类开了后门。

友元不宜多用

内部类

#include<iostream>
using namespace std;
class A
{
private:
 int _h = 1;
public:
 class B // B默认就是A的友元  B是内部类
 {
   public:
   void foo(const A& a)
  {
    cout << a._h << endl; //可以直接访问A中私有成员
  }
 
  int _b1;
 };
};

int main()
{
 cout << sizeof(A) << endl;
 A::B b;
 A aa;
 b.foo(aa);
 return 0;
}

如果一个类定义到一个类的内部,这个被定义的类就叫内部类

内部类中的函数可以访问外部类的私有,外部类不可以访问内部类的私有成员

class A
{
private:
 int _h = 1;
public:
 class B   // B默认就是A的友元  B是内部类
 {
   public:
   void foo(const A& a)
  {
    cout << a._h << endl; //可以直接访问A中私有成员
  }
 
  int _b1;
 };
};

int main()
{
   A aa;
   b.foo(aa);   //可以直接访问A中私有成员
   return 0;
}

在外部类外 表示内部类   需要在表示是从A这个外部类找到的B

 A::B b; 

外部类定义的对象中不包含内部类

 cout << sizeof(A) << endl;

这里输出结果为4  ,说明A这个类只给了4个字节的空间,如果A中包含B,需要的是8字节,侧面验证了外部类定义的对象中不包含内部类。

总结:

- 内部类和外部类有作用域上的包含关系
- 但访问权限仍然要按 C++ 访问控制规则来判断

- 外部类对内部类没有天然私有访问权

匿名对象

用类型(实参) 定义出来的对象叫做匿名对象,相比之前我们定义的 类型对象名(实参)定义出来的 叫有名对象

匿名对象生命周期只在当前一行,一般临时定义⼀个对象当前用⼀下即可,就可以定义匿名对象。

class A
{
public:
 A(int a = 0)
 :_a(a)
 {
 cout << "A(int a)" << endl;
 }
 ~A()
 {
 cout << "~A()" << endl;
 }
private:
 int _a;
};
class Solution {
public:
 int Sum_Solution(int n) {
 //...
 return n;
 }
};
int main()
{
 A aa1;
 // 但是我们可以这么定义匿名对象,匿名对象的特点不⽤取名字, 
 // 但是他的⽣命周期只有这⼀⾏,我们可以看到下⼀⾏他就会⾃动调⽤析构函数 
 A();
 A(1);
 A aa2(2); 
 Solution().Sum_Solution(10);
 return 0;

我们对比程序的结果来分析

首先创建了一个A的对象aa1

接着创建了2个匿名对象 ,我们看到2个匿名对象的生命周期只有一行,创建后,下一行就调用了析构函数。

利用匿名对象

Solution.Sum_Solution(10);

普通写法

Solution st;

st.Sum_Solution(10);

利用匿名对象后,写法更为简单,方便快捷。

在这种场景下的匿名对象的使用就非常香

更多推荐