C++ --> 类和对象(三)

简介: C++ --> 类和对象(三)

前言

前面已经对类和对象有一定的了解,接下来再次深入的了解一下。

一、深入理解构造函数

构造函数体赋值:

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化, 构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体 内可以多次赋值

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

初始化列表

初始化列表是在C++中构造函数中用于初始化类成员变量的一种机制。

  • 它以冒号(:)开始,后跟以逗号分隔的成员变量列表,每个成员变量后面可以跟一个初始化表达式。
class Date
{
  Date(int year, int month, int day)
    :_year(year)
    , _month(month)
    , _day(day)
  {}

private:
  int _year;
  int _month;
  int _day;
};

初始化列表使用的关键点:

  1. 初始化顺序:成员变量在初始化列表中的初始化顺序遵循它们在类定义中声明的顺序,而不是初始化列表中出现的顺序。
  • 严格执行声明顺序即初始化顺顺序,下面的代码在语法上先进行capacity的初始化。
    下面的程序会崩溃:
  1. 先进行_a的开辟空间,capacity没有进行初始化
  2. 导致capacity 是随机值。赖皮空间随机值。
class Stack
{
public:
    Stack(int capacity)//错误
    :_capacity(capacity)
    , _a((int*)malloc(sizeof(int)* _capacity))
    ,_size(0)
  {}
private:
  int* _a;
  int _capacity;
  int _size;
};

特定成员的初始化:对于常量成员、引用成员以及没有默认构造函数的自定义类型成员,必须在初始化列表中进行初始化,因为这些成员不能在构造函数体内被赋值。

  • 引用和const修饰都是在初始化定义的。
class A
{
public:
 A(int a)
 :_a(a)
 {}
private:
 int _a;
};
 
class B
{
public:
 B(int a, int ref)
 :_aobj(a)
 ,_ref(ref)
 ,_n(10)
 {}
private:
 A _aobj; // 没有默认构造函数
 int& _ref; // 引用
 const int _n; // const 
};
  1. 性能优势:使用初始化列表可以避免成员变量先默认构造后再赋值的额外步骤,特别是对于资源管理类(如持有指针或动态分配内存的类),这可以提高效率
  2. 成员变量的初始化只能进行一次,无论是在初始化列表中还是在构造函数体内,都不能重复初始化同一个成员变量。
class Date
{
  Date(int year, int month, int day)
    :_year(year)
    , _month(month)
    , _day(day)
//         ,_day(day)    //在加入这句就是错误的,初始化只能进行一次。
  {}

private:
  int _year;
  int _month;
  int _day;
};
  1. 成员变量的缺省值:如果在类成员声明时指定了缺省值,这些值将在进入构造函数体之前被用于初始化列表
  • 如果在定义的时候进行了传递参数,即优先使用传递的参数。
class Date
{
  Date(int year, int month, int day)
    :_year(year)
    , _month(month)
    , _day(day)
  {}

private:
  int _year = 1;
  int _month = 1;
  int _day = 1;
};
  1. 初始化列表的灵活性:除了初始化成员变量,初始化列表还可以用于动态内存分配、类型转换赋值等操作

二、explicit 关键字

explicit关键字用于修饰类的构造函数,以防止隐式类型转换。当一个构造函数被声明为explicit时,它不能参与隐式转换,只能通过直接初始化或拷贝初始化的方式显式调用。

class Date
{
public:
 // 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
 // explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译
 explicit Date(int year)
 :_year(year)
 {}
 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(2024);
 d2 = 2024;
}

三、static成员

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

static成员的特性

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

在下面的例子中:

  1. 没有this指针 func1 访问不了 func2
class Date
{
public:
  static void func1()
  {

  }
  void func2()
  {

  }

private:
  int _year;
  int _month;
  int _day;

  static int count;
};

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

四、友元

友元概念

  • 友元(friend)是一种特殊的关系,它允许一个函数或类访问另一个类的私有成员或保护成员。
  • 友元关系不是类的成员关系,而是一种独立的访问权限关系。
  • 通过友元,可以在不破坏封装性的前提下,实现类之间的数据共享和功能协作

修改print:

正常访问"重载操作<<",是访问不到 Date的private成员变量的,friend很好的帮助我们突破了这个界限。

ostream& operator<<(ostream& out,const Date& d)
{
  out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
  return out;
}

class Date
{
  friend  ostream& operator<<(ostream& out, const Date& d);

public:

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


private:
  int _year;
  int _month;
  int _day;
};


五、内部类

内部类的特点

  • 独立性:内部类是一个独立的类,不隶属于外部类。
  • 访问权限:内部类可以定义在外部类的任何访问级别中,其访问外部类成员的权限取决于其自身的定义位置。
  • 访问外部类成员:内部类可以直接访问外部类的staticenum成员,但不能直接访问外部类的非静态成员。
  • 大小关系:内部类的大小与外部类无关,sizeof(外部类)不会包括内部类的大小。
  • 创建方式:内部类的对象可以通过外部类的作用域限定符创建,例如外部类名::内部类名
  • 生命周期:内部类的对象可以在栈上或堆上创建,其生命周期取决于创建方式。
{
private:
 static int k;
 int h;
public:
 class B // B天生就是A的友元
 {
 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;
}

编程题目:友元的巧妙应用

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

数据范围: 0<�≤2000<n≤200

进阶: 空间复杂度 O*(1) ,时间复杂度 O*(n)

class Solution {
public:
    class B
    {
    public:
        B()
        {
            ret +=i;
            ++i;
        }

    };
int Sum_Solution(int n) {
    B a[n];

    return ret;
}

private:
static int ret ;
static int i;
};


int Solution:: ret = 0;
int Solution:: i = 1;

目录
相关文章
|
19天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
29 2
|
25天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
59 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
64 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
74 4
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
30 4
|
2月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
26 4
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
25 1
|
2月前
|
存储 编译器 C语言
【C++打怪之路Lv3】-- 类和对象(上)
【C++打怪之路Lv3】-- 类和对象(上)
17 0
|
2月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
2月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)