【C++技能树】类和对象的使用 --初始化列表,static,友元,内部类,匿名对象的理解与使用

简介: 虽然我们大多时候混淆初始化与赋值的概念,但在这里,构造函数体中只能成为赋值,因为初始化只能初始化一次,而赋值可以赋值多次。那么在哪里进行初始化呢?可能会说在定义时直接初始化,这在日期类中是可以的,但在这种情况当中,显然是不可以的了

Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法…感兴趣就关注我吧!你定不会失望。


类和对象的使用


cb63638ae1e949778936fff68b606c5a.png


0. 初始化列表


这是一个C++的默认构造函数


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


虽然我们大多时候混淆初始化与赋值的概念,但在这里,构造函数体中只能成为赋值,因为初始化只能初始化一次,而赋值可以赋值多次。那么在哪里进行初始化呢?可能会说在定义时直接初始化,这在日期类中是可以的,但在这种情况当中,显然是不可以的了


class A{
private:
    int __a;
};
class B{
private:
    int &_ref;
    const int _ci;
};


根据前面所学,引用&与限制const必须在定义时就完成初始化。那么我这里该如何初始化呢?我并没有确定我的要使用的对象是什么。

这就是初始化列表存在的意义了:

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


class B{
public:
    B(int& ref,int ci):
        _ref(ref),
        _ci(ci)
    {
    }
private:
    int &_ref;
    const int _ci;
};


所以,这样解决了引用&与限定const的初始化问题:可以根据具体场景进行初始化了。

注意:


1.每个成员变量仅能在初始化列表中出现一次!因为初始化只能初始化一次


2.类中包含 引用、const成员变量及自定义类型成员(且该类没有默认构造函数时),必须将其放在初始化列表中进行初始化!


class A{
public:
    A(int a):
        __a(a)
    {
    }
private:
    int __a;
};
class B{
public:
    B(int a,int& ref,int ci):
        _a(a),
        _ref(ref),
        _ci(ci)
    {
    }
private:
    A _a;
    const int _ci;
    int &_ref;
};


例如在这个例子中,B类的属性有三个,分别对应了自定义类型成员,引用,const的三种情况。所以需要将其放在初始化列表中进行初始化。


其初始化顺序为,从成员属性去找对应的初始化列表。


91b48049cc644519b8ead533ecb48715.png


这是一个初始化列表的特性,当遇到以下这种情况的时候就需要充分考虑到这种特性。


class bug
{
public:
    bug(int a):
    _a1(a),
    _a2(_a1)
    {
    }
private:
    int _a2;
    int _a1;
}


当传入的a=1时_a2与_a1的结果是什么呢?答案是a2为乱码,a1为1,这就是因为初始化列表的初始化顺序是根据声明顺序来进行的,所以导致了这个问题。


在日常中建议多用初始化列表,因为即使在声明时给上初始值,之后也会调用初始化列表进行初始化。


那初始化列表这么有用,是不是在函数体内进行声明就没有使用场景了?


class test2
{
public:
    test2():
    a((int *)malloc(sizeof(int)*10))
    {
        if(a==NULL)
        {
            perror("malloc,failed");
        }
    }
private:
    int *a;
};
int main()
{
   test2 a;
    return 0;
}


在这段代码中,虽然初始化列表可以完成对a的空间分配,但malloc可能存在分配失败的问题。所以需要对其分配完的空间进行一个检查,此时初始化列表就不能做这件事了。


所以,初始化列表只适用于初始化变量,这一件事,其他事仍然需要放在函数体内执行


explicit关键字


class trans{
public:
    trans(int a=0)
    {
        _a=a;
        cout<<"trans()";
    }
    trans(const trans& T)
    :_a(T._a)
    {
        cout<<"trans(const trans&T)";
    }
    void print()
    {
        cout<<_a<<endl;
    }
private:
    int _a;
};
int main()
{
    trans T1(1);
    trans T2=2;
    const trans& T3=2;
    return 0;
}


T1与T2的初始化非常的常见,前面也介绍了很多类似的用法,但需注意,T2此时有一个隐式类型转换:

97032debfca340809efadda34de0f592.png


会先将内置类型调用一次构造函数,将其变为trans对象,之后再调用一次初始化构造,完成对T2的初始化。(但经过编译器优化后这种行为只会出现一次)

所以T3为什么需要加上const?因为在完成转换后,临时构造出的trans会被销毁,而&又不能引用一个销毁的对象,所以需要const延长其生命周期。

当然,我们如果不想发生这种隐式类型转换的行为,可以在构造函数前加入 explicit的关键字。


class trans{
public:
    explicit trans(int a=0)
    {
        _a=a;
        cout<<"trans()";
    }
    trans(const trans& T)
    :_a(T._a)
    {
        cout<<"trans(const trans&T)";
    }
    void print()
    {
        cout<<_a<<endl;
    }
private:
    int _a;
};
int main()
{
    trans T1(1);
    trans T2=2;
    return 0;
}


1.Static静态成员变量


看看以下这个程序,创建了多少类对象。


class sta{
public:
    sta(){
        _count++;
    }
    sta(const sta&st)
    {
        _count++;
    }
    ~sta()
    {
        --_count;
    }
    static int _count;
};
int sta::_count=0;
sta aa0;
void Func()
{
  static sta aa2;
  cout << __LINE__ << ":" << sta::_count << endl;
  sta::_count++;
}
int main()
{
  cout <<__LINE__<<":"<< sta::_count << endl;  // 1
  sta aa1;
  Func();  // 3
  Func();  // 3
  return 0;
}


从中可以看出static对象的作用:对于全局变量进行了进一步的封装:


  1. 静态成员为所有类对象所共享,不属于某个具体的对象
  2. 静态成员变量在类内声明,在类外定义,定义时不加static关键字
  3. 可以通过类名::变量名 或者 对象.静态成员来访问
  4. 静态成员函数没有this指针,所以不能访问任何非静态成员
  5. 静态成员也是类的成员,受访问限定符限制

一道用到了static变量特性的oj题

314c59c472af4656b8585b3f5c545a21.png

class sum
{
public:
    sum()
    {
        n++;
        s+=n;
    }
    static int s;
    static int n;
};
int sum:: s=0;
int sum:: n=0;
class Solution {
public:
    int Sum_Solution(int n) {
        sum s1[n];
        return sum::s;
    }
};


2.友元


2.1.友元函数


友元函数可以直接访问类的私有成员,使用时在类中的任意位置声明即可,


class test0
{
public:
private:
    int score=100;
};
void print(const test0& t0)
{
    cout<<t0.score<<endl;
}


在这里,是无法访问score这个变量的,因为score为私有成员属性,所以无法在类外进行访问.但加入友元函数的声明后可以突破这个限制


class test0
{
friend void print(const test0&t0);
public:
private:
    int score=100;
};
void print(const test0& t0)
{
    cout<<t0.score<<endl;
}


  1. 友元函数可以访问类的私有成员和保护成员,但其不是类的成员函数
  2. 友元函数因为不是类的成员函数,没有this指针,所以不可以且没必要用后置const进行修饰
  3. 友元函数可以在类定义的任何地方进行声明
  4. 一个函数可以与多个类达成友元关系
  5. 调用原理与普通函数相同

2.2.友元类


在一个类中声明加入另一个类的友元声明,在另一个类中即可访问这个类受保护的成员变量.


class test0
{
friend void print(const test0&t0);
friend class test3;
public:
private:
    int score=100;
};
class test3
{
public:
    void print()
    {
        cout<<t0.score;
    }
private:
    test0 t0;
};


1.友元关系是单向的,test3可以访问test0的成员变量,反之却不可以

2.友元关系不能传递


3.内部类


一个类定义定义在一个类的里面.


class A
{
public:
    class B{
    private:
        int _b;
    };
private:
    static int _a1;
    int _a2;
};
int A:: _a1=0;
int main()
{
  return 0;
}


计算A的大小时,仅计算A中的成员,例如这里sizeof(A)=4.


内部类是外部类的友元,访问静态变量的时候不需要加类作用域修饰符

这里B是A的友元,但A不是B的友元


class A
{
public:
    class B{
    public:
        void print(const A&a)
        {
            cout<<a._a2<<_a1;
        }
    private:
        int _b;
    };
private:
    static int _a1;
    int _a2;
};


4.匿名对象


当想使用一个类中的某一个函数时可以直接使用匿名对象.


class C{
public:
    void print()
    {
        cout<<6;
    }
};
int main()
{
    C a; 
    a.print();
  C().print();
}


正常的调用需要像main中前两行写一样,但加入了匿名函数的规则之后,直接按照第三个规则来写即可.匿名函数的生命周期仅在当前行.

void print(const A&a)
        {
            cout<<a._a2<<_a1;
        }
    private:
        int _b;
    };
private:
    static int _a1;
    int _a2;
};


4.匿名对象


当想使用一个类中的某一个函数时可以直接使用匿名对象.


class C{
public:
    void print()
    {
        cout<<6;
    }
};
int main()
{
    C a; 
    a.print();
  C().print();
}


正常的调用需要像main中前两行写一样,但加入了匿名函数的规则之后,直接按照第三个规则来写即可.匿名函数的生命周期仅在当前行.

至此初始化列表,static,友元,内部类,匿名对象的理解与使用结束

目录
相关文章
|
16天前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
58 6
|
4月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
172 5
|
5月前
|
存储 编译器 数据安全/隐私保护
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解2
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
80 3
|
5月前
|
编译器 C++
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解1
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
85 3
|
5月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
59 1
|
5月前
|
C++
C++入门4——类与对象3-2(构造函数的类型转换和友元详解)
C++入门4——类与对象3-2(构造函数的类型转换和友元详解)
55 0
|
1月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
12天前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
39 16
|
5天前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
5天前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。