learn C++ NO.6——类和对象(4)

这篇具有很好参考价值的文章主要介绍了learn C++ NO.6——类和对象(4)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

1.2.初始化列表

类的构造函数可以使用初始化列表来初始化成员变量。初始化列表位于构造函数的参数列表之后,使用冒号分隔。初始化列表的语法格式为:成员变量名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;
};
int main()
{
	//类对象的定义
	Date d1(2023,5,20);
    return 0;
}

初始化列表本质上就是类对象的成员变量定义的地方。为什么类对象的成员变量一定就需要定义的地方呢?且听下面分析。

1.2.1初始化列表的特点

1、类中如果包含以下类型成员变量,必须放在初始化列表位置进行初始化。

1、引用成员变量
2、const成员变量
3、自定义类型成员变量(且该类没有默认构造函数)

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
    int x = 1;//这是声明缺省值
};

2、每个成员函数有且只有一次初始化。
learn C++ NO.6——类和对象(4)
3、尽可能的使用初始化列表去做初始化工作,因为不管我们是否使用初始化列表,对于自定义类型成员变量来说,一定会先使用初始化列表来进行初始化。

class Time
{
public:
    Time(int hour = 0)
        :_hour(hour)
    {
        cout << "Time()" << endl;
    }

private:
    int _hour;
};

class Date
{
public:
    Date(int day)
    {}
private:
    int _day;
    Time _t;
};

4、 初始化列表并不能完全替代构造函数体内赋值。

class Stack
{
public:
    Stack(int capacity = 10)
        : _a((int*)malloc(capacity * sizeof(int)))
        ,_top(0)
        ,_capacity(capacity)
        //初始化列表并不能完成所有初始化场景
    {
    	//判断有效性
        if (nullptr == _a)
        {
            perror("malloc申请空间失败");
            exit(-1);
        }

        // 要求数组初始化一下
        memset(_a, 0, sizeof(int) * capacity);
    }
private:
    int* _a;
    int _top;
    int _capacity;
};

5(重点)、成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

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();
    
    return 0;
}
//A.输出1  1
//B.程序崩溃
//C.编译不通过
//D.输出1  随机值

这里我们来分析一下代码,首先,会调用初始化列表来初始化对象的成员变量。由于类的成员变量的声明顺序为a2、a1。所以,这里初始化列表先用a1的值来初始化a2。但是,a1此时还是随机值,故a2的值为随机值。而a1会被初始化成1。答案为D。

1.3.explicit关键字

这里在正式介绍explicit关键字前,先介绍一下类类型的隐式类型转换。请看下面样例。

class A
{
public:


    A(int a)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }

    A(const A& aa)
        :_a(aa._a)
    {
        cout << "A(const A& aa)" << endl;
    }

private:
    int _a;
};

int main()
{
    A aa1(1);
    A aa2 = 2; // 隐式类型转换,整形转换成自定义类型
    

    A& aa3 = 2;
    // error C2440: “初始化”: 无法从“int”转换为“A &”
    const A& aa3 = 2;
    
    return 0;
}

learn C++ NO.6——类和对象(4)
learn C++ NO.6——类和对象(4)
pxplicit关键字可以禁止这类的类型转化。在A的构造函数前加上explicit关键字后,aa2和aa3便不能定义,编译器报错,没有显式的类型转化。
learn C++ NO.6——类和对象(4)

2.静态成员

2.1.静态成员的概念

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

class A
{
    
private:
    static int _count;
};

//必须在类外定义
//只有定义时,可以突破一次类域限制
int A::_count = 0;

访问 _count变量必须是在类域内才能访问。当我们需要在类域外访问该静态成员变量时,可以借助静态成员函数来实现访问。

class A
{
public:
    static int Getcount()
    {
        return  _count;
    }
    
private:
    static int _count;
};

//必须在类外定义
//只有定义时,可以突破一次类域限制
int A::_count = 0;

int main()
{
    //需要指明类域进行访问静态成员函数
    cout << A::Getcount() << endl;
    return 0;
}

2.2.经典试题:计算程序中运行到cout处时,创建出了多少个类对象。

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;


A a1;

void Func()
{
    A a;
    static A aa;
}

void TestA()
{
    cout << A::GetACount() << endl;
    A a1, a2;
    Func();
    cout << A::GetACount() << endl;
}

int main()
{
    TestA();
    return 0;
}

在程序一开始,便在全局定义了第一个类对象a1,所以第一个cout执行结果为1。紧接着,定义了两个局部类对象分别是a1,a2。需要注意的是a1和全局的a1不冲突,因为处于不同的作用域范围。执行Func函数,定义了一个局部类对象a和静态类对象aa。Func函数调用结束,局部对象销毁。而静态对象的是定义在静态区的,不会随着函数调用的结束而销毁。所以,第二个cout执行结果为4。

2.3.静态成员的特性

1、静态成员是被所有类的对象共享的,它不属于某个具体的类对象,它是属于整个类,存放在静态区中。
2、静态成员变量必须定义在类外面,定义时不添加static关键字,类中存放的是声明。
3、类静态成员可以用类名::静态成员或者对象.静态成员来进行访问。
4、因为静态成员函数是没有隐含的this指针,所以不能访问非静态的类成员。
5、静态成员也是类成员,会受到public、private、protect访问限定符的限制。

2.4.两个关于静态成员函数和非静态成员函数调用问题

1、静态成员函数可以调用非静态成员函数吗?
答案是不可以,因为静态成员函数没有this指针。调用非静态成员函数的参数部分需要隐含this指针,如果在非静态成员函数内部有对类的成员变量进行访问就会报错。
2. 非静态成员函数可以调用类的静态成员函数吗?
答案是可以。因为非静态成员函数也是类一部分,在类的作用域内调用静态成员函数是OK的。

2.5.简单提及单例类的思想

假设我们需要设计一个只能在栈上或者堆上开辟类对象的方法。我们需要怎么做呢?首先,我们要讲构造函数私有,通过静态成员函数的调用来实现这一功能。

class A
{
public:
    static A GetStackObj()
    {
        A aa;
        return aa;
    }

    static A* GetHeapObj()
    {
        return new A;
    }
private:
    //无法直接构造
    A()
    {}

private:
    int _a1 = 1;
    int _a2 = 2;
};

int main()
{

    A aa1 = A::GetStackObj();//栈上对象
    A aa2 = *(A::GetHeapObj());//堆上对象
    return 0;
}

3.友元

3.1.友元的概念

友元(friend)是C++中的一个关键字,用于实现类之间的访问控制。在C++中,类可以将其他类或函数声明为友元,从而让它们访问自己的私有成员和保护成员。被声明为友元的类或函数可以直接访问另一个类的私有成员和保护成员,而不需要通过该类的公有接口来访问。友元的使用可以增加程序的灵活性和可扩展性,但也会增加程序的耦合度和不安全性。因此,在使用友元时需要谨慎考虑,并遵循最小暴露原则,尽可能地减少对其他类的访问控制。

3.2.友元函数

在前文日期类的实现中,就已经提到了<<运算符和>>运算符重载需要声明友元。这是因为,重载类对象成员函数,this指针会占用默认的第一个参数。没法实现类似cout << obj这样使用库函数。这时候就需要友元函数声明,并将运算符重载到全局。

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

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

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

友元函数的说明:
1、友元函数不能被const修饰。
2、友元函数可以在类定义的任何地方声明,不受访问限定符的限制。
3、一个函数可以使多个类的友元函数。
4、友元函数的调用和普通函数的调用原理相同

3.3.友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类的所有成员函数。友元关系是单向的,不具有交换性。友元也不具备传递性,即若B是A的友元,C是B的友元,不能说明C是A的友元。友元关系不能继承,这里我们暂时不谈,需要了解有这一概念即可。

class A
{
    friend class B;//声明B是我的友元,B内可以访问A的成员
    //而A内不能访问B的成员
    
private:
    int _a;
    double _d;
protected:
    char _c;
};

class B
{
public:
    int GetAi()
    {
        return _ca._a;
    }
    double GetAd()
    {
        return _ca._d;
    }
    char GetAc()
    {
        return _ca._c;
    }
private:
    int _i;
    A _ca;
};

4.内部类

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

4.1.内部类的特性

1、内部类可以定义在外部类的任意处,无论public、private、protected都是可以的。

class A
{
private:
    static int k;
    int h;
public:
    class B// B天生就是A的友元
    {
    public:
        void foo(const A& a)
        {
            cout << k << endl;//可以直接访问static成员
            cout << a.h << endl;//OK
        }
    };
    
};

int A::k = 1;

int main()
{
    A::B _b;
    cout<<sizeof(A)<<endl;//4字节
    return 0;
}

这里k是存储在静态区中,所以不计入sizeof的大小。内部类可以直接访问外部类的static成员,不需要外部类的对象/类名。

5.匿名对象

C++中的匿名对象指的是没有命名的临时对象,该对象是在表达式中创建的,用于执行某些操作并返回结果

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

int main()
{
    A a(10);
    A(10);//匿名对象,声明周期仅在表达式行内
    return 0;
}

5.1.匿名对象生命周期的延长

int main()
{
    A(10);//匿名对象,声明周期仅在表达式行内
    const A& ra = A(10);
    A(10)
    return 0;
}

learn C++ NO.6——类和对象(4)
当我们将一个匿名对象赋值给一个 const 引用时,编译器会将这个匿名对象的生命周期延长到引用的作用域范围内。这是因为当我们定义一个 const 引用时,编译器会在内存中分配一个临时变量来存储这个引用所指向的值,而这个临时变量的生命周期和 const 引用的作用域范围相同。因此,当我们将一个匿名对象赋值给 const 引用时,编译器会将这个匿名对象的生命周期延长到 const 引用的作用域范围内,以保证 const 引用能够正确地引用这个对象。这样做可以避免出现悬垂指针的问题,保证程序的安全性和正确性。

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)
{

}

int main()
{
	A aa1;
	Func1(aa1);
	Func2(aa1);
	return 0;
}

首先,创建aa1对象会调用一次构造函数。而调用Func1函数,在传参的过程中会产生临时变量。将形参拷贝给实参,会调用拷贝构造。而Func2传参并不会调用拷贝构造,因为传的是引用,不会开辟临时空间。
learn C++ NO.6——类和对象(4)
请看下面的场景。

//class A...
//这里我就不再写了

void Func1(A aa)
{}

A Func5()
{
    A aa;
    return aa;
}

int main()
{
    A ra1 = Func5(); // 拷贝构造+拷贝构造 ->优化为拷贝构造
    cout << "==============" << endl;
    A ra2;
    ra2 = Func5();
	return 0;
}

learn C++ NO.6——类和对象(4)
对于同一行内的连续的构造+拷贝构造编译器会进行优化,优化为直接构造。而ra2这样的定义的方式,对于内置类型来说会连续调用三次构造函数,对程序性能有所影响。建议对于自定义类型的定义采用ra1类似的方式。

learn C++ NO.6——类和对象(4)文章来源地址https://www.toymoban.com/news/detail-460750.html

到了这里,关于learn C++ NO.6——类和对象(4)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】类和对象③(类的默认成员函数:拷贝构造函数 | 赋值运算符重载)

    🔥 个人主页: Forcible Bug Maker 🔥 专栏: C++ 目录 前言 拷贝构造函数 概念 拷贝构造函数的特性及用法 赋值运算符重载 运算符重载 赋值运算符重载 结语 本篇主要内容:类的6个默认成员函数中的 拷贝构造函数 和 赋值运算符重载 在上篇文章中我们讲到了类的默认成员函数的

    2024年04月17日
    浏览(37)
  • 【C++】类和对象④(类的默认成员函数:取地址及const取地址重载 | 再谈构造函数:初始化列表,隐式类型转换,缺省值)

    🔥 个人主页: Forcible Bug Maker 🔥 专栏: C++ 目录 前言 取地址及const取地址操作符重载 再谈构造函数 初始化列表 隐式类型转换 explicit 成员变量缺省值 结语 本篇主要内容:类的六个默认成员函数中的 取地址 及 const取地址重载 , 构造函数 初始化列表 , 隐式类型转换

    2024年04月26日
    浏览(36)
  • learn C++ NO.4 ——类和对象(2)

    在 C++ 中,如果没有显式定义类的构造函数、析构函数、拷贝构造函数和赋值运算符重载函数,编译器会自动生成这些函数,这些函数被称为默认成员函数。 初步了解了默认成员函数,上面的空类Date,其实在程序运行时,编译器会默认生成它的默认成员函数。 小结 当没有显

    2024年02月05日
    浏览(24)
  • learn C++ NO.6——类和对象(4)

    1.1.构造函数体赋值 在创建类的对象时,编译器回去调用类的构造函数,来各个成员变量一个合适的值。 虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。

    2024年02月06日
    浏览(26)
  • 【C++初阶】类和对象——构造函数&&析构函数&&拷贝构造函数

    ========================================================================= 个人主页点击直达: 小白不是程序媛 C++系列专栏: C++头疼记 ========================================================================= 目录 前言 类的6个默认成员函数 构造函数 概念 构造函数的特性 析构函数 概念 析构函数特性 拷贝构

    2024年02月06日
    浏览(41)
  • C++ 类和对象(二)构造函数、析构函数、拷贝构造函数

            本文将介绍类的6个默认成员函数中的构造函数、析构函数和拷贝构造函数,赋值重载和取地址重载涉及运算符重载的知识,将在下篇讲解。所谓默认成员函数,也就是每个类都有的成员函数,我们可以显式定义这些函数,否则,编译器会自动生成它们。 目录 前言

    2024年02月09日
    浏览(35)
  • C++ 类和对象(中)构造函数 和 析构函数

     上篇链接:C++ 类和对象(上)_chihiro1122的博客-CSDN博客  我们在C当中,在写一些函数的时候,比如在栈的例子: 如上述例子,用C++ 返回这个栈是否为空,直接返回的话,这栈空间没有被释放,就会内存泄漏,如果我们直接释放这个栈,那么我们就拿不到这个栈是否为空的

    2024年02月02日
    浏览(31)
  • 【C++】类和对象(中)---构造函数和析构函数

    个人主页:平行线也会相交💪 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【C++之路】💌 本专栏旨在记录C++的学习路线,望对大家有所帮助🙇‍ 希望我们一起努力、成长,共同进步。🍓 我们知道类包含成员变量和成员函数,当一个类中

    2024年02月05日
    浏览(91)
  • 【c++】类和对象(三)构造函数和析构函数

    🔥 个人主页 :Quitecoder 🔥 专栏 :c++笔记仓 朋友们大家好,本篇文章我们带来类和对象重要的部分, 构造函数和析构函数 如果一个类中什么成员都没有,简称为空类 任何类在什么都不写时,编译器会自动生成以下6个默认成员函数 (用户没有显式实现,编译器会生成的成

    2024年04月12日
    浏览(30)
  • 【c++】类和对象(四)深入了解拷贝构造函数

    🔥 个人主页 :Quitecoder 🔥 专栏 :c++笔记仓 朋友们大家好啊,本篇内容带大家深入了解 拷贝构造函数 拷贝构造函数是一种特殊的构造函数,在对象需要以同一类的另一个对象为模板进行初始化时被调用。它的主要用途是初始化一个对象,使其成为另一个对象的副本 我们先

    2024年04月16日
    浏览(57)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包