C++类和对象(三)

简介: C+类和对象(三)1. 再聊构造函数1.1 构造函数体内赋值

C+类和对象(三)

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

为什么有初始化列表呢?

const和引用成员变量和非默认构造函数需要初始化,就必须找一个定义的位置来初始化,这里就需要用到初始化列表

class B
{
public:
  B(int x) //非合适默认构造函数(无缺省参数)
    :_b(x)
  {
    cout << "B(int x)" << endl;
  }
private:
  int _b;
};
class A
{
public:
  A(int r = 0, int y = 0)
    :_a(10) //定义+初始化
    ,_ref(r) //定义+初始化
    , _b(y)
  {}
private:
  const int _a; //声明
  int& _ref; //声明
  B _b; //没有合适的默认构造函数
};
  1. 对象调用构造函数,初始化列表就是该对象定义(开空间)所有成员变量的位置
  2. 不管是否在初始化列表中写定义,编译器都会对每个成员变量在初始化列表定义并初始化

成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

class A
{
public:
  A(int a)
    :_a1(a)
    , _a2(_a1)
  {}
  void Print() {
    cout << "_a1=" << _a1 << " " << "_a2="  << _a2 << endl;
  }
private:
  int _a2;
  int _a1;
};
int main() {
  A aa(1);
  aa.Print();
}
//_a1 = 1 _a2 = 随机值

特性

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)


类中包含以下成员,必须放在初始化列表位置进行初始化:


引用成员变量

const成员变量

自定义类型成员(且该类没有默认构造函数时)

成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

1.3 explicit关键字

背景:类型转换

int i = 1;
double d = i; //内置类型转换 (中间有临时变量存储,临时变量具有常性,仿佛别const修饰)
class A
{
public:
  A(int a)
    :_a(a)
  {}
private:
  int _a;
};
int main()
{
  A a(1); //构造函数调用
  A b = 1; //1是int类型,b是A自定义类型(类型转换,中间产生临时变量)
  return 0;
}

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

//单参数就是上述代码,多参数如下
class A
{
public:
   A(int x, int y = 1, int z = 2) 
    :_a(x)
    ,_b(y)
    ,_c(z)
   {}
private:
  int _a;
  int _b;
  int _c;
};
int main()
{
  A c = { 1,2,3 };
  return 0;
}

如果不想让其自定义类型转换呢?

用explicit修饰构造函数,将会禁止构造函数的隐式转换

class A
{
public:
  explicit A(int a)
    :_a(a)
  {}
private:
  int _a;
};
int main()
{
  A a = 1; //err(“初始化”: 无法从“int”转换为“A”)
  return 0;
}

2. static成员

2.1 特性

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

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

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

静态成员函数是存放在静态区的,没有隐藏的this指针,不能访问任何非静态成员

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

2.2 实现计算类对象个数的类

class A
{
public:
  A(){ //构造
    ++_count;
  }
  A(const A& a) { //拷贝构造
    ++_count;
  }
  ~A() {
    --_count;
  }
  static int getCount() { //静态成员函数,没有this指针
    return _count;
  }
private:
  static int _count;//静态区
};
int A::_count = 0; //初始化
int main()
{
    //cout << A::_count << endl;
    cout << A::getCount << endl; //静态成员函数在静态区,不需要实例化对象再调用函数
    return 0;
}

问题

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

不行,静态成员函数是存放在静态区的,没有隐藏的this指针,不能访问任何非静态成员

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

可以,静态成员函数是存放在静态区的,没有隐藏的this指针,不能访问任何非静态成员

3. 友元函数和友元类

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

友元不宜多用

3.1 友元函数

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在

类的内部声明,声明时需要加friend关键字

class A
{
  friend int getStudentVarASum(); //友元函数声明(是A类的友元)
public:
  A(int x = 0, int y = 1)
    :_a(x)
    , _b(y)
  {}
private:
  int _a;
  int _b;
};
int getStudentVarASum() {
  return A()._a + A()._b;
}
int main()
{
  cout << getStudentVarASum() << endl;
  return 0;
}

注意

  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数

3.2 友元类

class Time
{
  // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量,但是Time类就不能访问日期类
  friend class Date; 
public:
  Time(int hour = 0, int minute = 0, int second = 0)
    : _hour(hour)
    , _minute(minute)
    , _second(second)
  {}
private:
  int _hour;
  int _minute;
  int _second;
};
class Date
{
public:
  Date(int year = 1900, int month = 1, int day = 1)
    : _year(year)
    , _month(month)
    , _day(day)
  {}
  void SetTimeOfDate(int hour, int minute, int second)
  {
    // 直接访问时间类私有的成员变量
    _t._hour = hour;
    _t._minute = minute;
    _t._second = second;
  }
private:
  int _year;
  int _month;
  int _day;
  Time _t;
};

注意

  1. 友元关系是单向的,不具有交换性
  2. 友元关系不能传递:如果C是B的友元, B是A的友元,则不能说明C时A的友元
  3. 友元关系不能继承

4.内部类

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元

特性

  1. 内部类可以定义在外部类的public、protected、private都是可以的 ,受类域限定符的限制
  2. 注意内部类可以直接访问外部类中的static成员,因为是静态区的,不需要外部类的对象/类名
  3. sizeof(外部类+内部类)=sizeof(外部类),和内部类没有任何关系
class A
{
private:
  static int k;
  int h;
public: 
  class B  // B天生就是A的友元
  {
  public:
    void foo(const A& a)
    {
      cout << k << endl; 
      cout << a.h << endl; 
    }
  };
};
int A::k = 1;
class C
{
public:
  class D
  {
  private:
    int c;
    int d;
  };
private:
  int a;
  int b;
};
int main()
{
  A::B b;
  b.foo(A()); 
  //A::B().foo(A()); //匿名对象调用
  //A().foo(A()); //err:"foo": 不是 "A" 的成员
  cout << sizeof(C) << endl; //输入8,C类不包含内部类D
  return 0;
}

5.匿名对象

5.1 特性

  1. 结构:类名()
  2. 声明周期就是调用完后结束

5.2 基本语法

class A
{
public:
  A(int a = 0)
    :_a(a)
  {
    cout << "A(int a)" << endl;
  }
  ~A()
  {
    cout << "~A()" << endl;
  }
private:
  int _a;
};
int main()
{
  A(); //匿名对象,声明周期就是本行
  return 0;
}

202302112129253 (1).png

5.3 使用场景

//使用场景有很多,下面是一个常见的
class solution {
public:
  int sum_solution(int n) {
    //...
    return n;
  }
};
int main()
{
  solution().sum_solution(10); //创建对象太麻烦,不创建对象直接调用成员函数
  return 0;
}

6. 拷贝对象时编译器的优化

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 func1(A aa){}
void func2(const A& aa){}
A func3()
{
  A aa;
  return aa; 
}
A func4()
{
  return A();
}
int main()
{
  A aa1 = 1; // 构造+拷贝构造 -》 优化为直接构造
  func1(aa1); // 无优化
  func1(2); // 构造+拷贝构造 -》 优化为直接构造
  func1(A(3)); // 构造+拷贝构造 -》 优化为直接构造
  func2(aa1);  // 无优化
  func2(2);    // 无优化
  func2(A(3)); // 无优化
  func3(); //构造+拷贝构造(无优化)
  A aa1 = func3(); // 拷贝构造+拷贝构造  -- 优化为一个拷贝构造
  cout << "****" << endl;
  A aa2;
  aa2 = func3();  // 不能优化(赋值接收)
  func4(); // 构造+拷贝构造 -- 优化为构造
  A aa3 = func4(); // 构造+拷贝构造+拷贝构造  -- 优化为构造
  return 0;
}

总结

  1. 对象返回时,接收返回值对象,尽量拷贝构造方式接收,不要赋值接收
  2. 对象返回时,函数中返回对象时,尽量返回匿名对象
  3. 函数传参时,尽量使用const &传参
























相关文章
|
13天前
|
C++
C++(十一)对象数组
本文介绍了C++中对象数组的使用方法及其注意事项。通过示例展示了如何定义和初始化对象数组,并解释了栈对象数组与堆对象数组在初始化时的区别。重点强调了构造器设计时应考虑无参构造器的重要性,以及在需要进一步初始化的情况下采用二段式初始化策略的应用场景。
|
13天前
|
存储 编译器 C++
C ++初阶:类和对象(中)
C ++初阶:类和对象(中)
|
13天前
|
C++
C++(十六)类之间转化
在C++中,类之间的转换可以通过转换构造函数和操作符函数实现。转换构造函数是一种单参数构造函数,用于将其他类型转换为本类类型。为了防止不必要的隐式转换,可以使用`explicit`关键字来禁止这种自动转换。此外,还可以通过定义`operator`函数来进行类型转换,该函数无参数且无返回值。下面展示了如何使用这两种方式实现自定义类型的相互转换,并通过示例代码说明了`explicit`关键字的作用。
|
13天前
|
存储 设计模式 编译器
C++(十三) 类的扩展
本文详细介绍了C++中类的各种扩展特性,包括类成员存储、`sizeof`操作符的应用、类成员函数的存储方式及其背后的`this`指针机制。此外,还探讨了`const`修饰符在成员变量和函数中的作用,以及如何通过`static`关键字实现类中的资源共享。文章还介绍了单例模式的设计思路,并讨论了指向类成员(数据成员和函数成员)的指针的使用方法。最后,还讲解了指向静态成员的指针的相关概念和应用示例。通过这些内容,帮助读者更好地理解和掌握C++面向对象编程的核心概念和技术细节。
|
26天前
|
存储 算法 编译器
c++--类(上)
c++--类(上)
|
1月前
|
编译器 C++
virtual类的使用方法问题之C++类中的非静态数据成员是进行内存对齐的如何解决
virtual类的使用方法问题之C++类中的非静态数据成员是进行内存对齐的如何解决
|
1月前
|
编译器 C++
virtual类的使用方法问题之静态和非静态函数成员在C++对象模型中存放如何解决
virtual类的使用方法问题之静态和非静态函数成员在C++对象模型中存放如何解决
|
1月前
|
编译器 C++
virtual类的使用方法问题之在C++中获取对象的vptr(虚拟表指针)如何解决
virtual类的使用方法问题之在C++中获取对象的vptr(虚拟表指针)如何解决
|
13天前
|
存储 C++
C++(五)String 字符串类
本文档详细介绍了C++中的`string`类,包括定义、初始化、字符串比较及数值与字符串之间的转换方法。`string`类简化了字符串处理,提供了丰富的功能如字符串查找、比较、拼接和替换等。文档通过示例代码展示了如何使用这些功能,并介绍了如何将数值转换为字符串以及反之亦然的方法。此外,还展示了如何使用`string`数组存储和遍历多个字符串。
|
22天前
|
存储 C++
C++ dll 传 string 类 问题
C++ dll 传 string 类 问题
16 0