【C++初阶】类和对象(下)

【C++初阶】类和对象下

🥕个人主页:开敲🍉

🔥所属专栏:C++🥭

🌼文章目录🌼

1. 再谈构造函数

2. 类型转换

3. static成员

4. 友元

5. 内部类

1. 再谈构造函数

 之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有一种方式,就是初始化列表,初始化列表的使用方式是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟⼀个放在括号中的初始值或表达式。

 每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。 

 引⽤成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。

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

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

⑥ 初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。

  没有默认构造的自定义类类型变量、static修饰的变量和引用变量必须在初始化列表中初始化,否则会报错。

错误:

正确:

  下面再来看一段代码

  思考一下,上面那里是初始化吗?

  注意:上面那里不是初始化,而是给一个缺省值,这个缺省值是给到初始化列表的。如果在初始化时给定了一个值,那么就会初始化为这个值;如果没给,则会默认使用这个缺省值初始化。

  来看看下面一段代码的输出结果是什么:

A. 输出1 1  B. 输出2  2   C. 编译报错  D. 输出1 随机值  E. 输出1  2  F. 输出2  1

答案:D

解释:初始化的顺序是根据声明的顺序来的,这里我们先声明的是_a2,因此我们在初始化时也是会先初始化_a2,而_a2又是由_a1初始化,但是_a1我们还并没有初始化,因此_a1是随机值,所以_a2会被初始化为随机值;而后初始化_a1,_a1是由参数a初始化的,因此_a1初始化为1,故答案为D。

2. 类型转换

 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;
};
int main()
{
    // 1构造⼀个A的临时对象,再用这个临时对象拷贝构造aa3
    // 编译器遇到连续构造+拷贝构造->优化为直接构造

    A aa1 = 1;
    aa1.Print();
    const A& aa2 = 1;
    // C++11之后才支持多参数转化
    A aa3 = { 2,2 };
    return 0;
}

3. static成员

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

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

错误:

练习题:求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)

//思路:利用每次创建类类型对象都会调用构造函数的特点,我们在构造函数中完成等差数列累加功能。这里需要创建一个类类型的变长数组,用于多次创建类类型对象。

//用于完成等差数列的累加

class Sum

{

public:

//每次创建类类型对象都会调用构造函数,因此创建一个构造函数,在构造函数中完成

//等差数列累加的功能

//特别的:这里需要使用静态变量

//上面说过,类内声明的static变量必须在类外进行定义

    Sum()

    {

        x+=y;

        y++;

    }

    static int GetX()

    {

        return x;

    }

private:

    static int x;

    static int y;

};

int Sum::x = 0;

int Sum::y = 1;

class Solution {

public:

    int Sum_Solution(int n)

    {

//Sum类的变长数组,用于多次创建类类型对象

        Sum arr[n];

        return Sum::GetX();

    }

};

  再来看看下面的两个问题:

设已经有A、B、C、D四个类的定义,程序中A、B、C、D构造函数调用顺序为?()

 设已经有A、B、C、D四个类的定义,程序中A、B、C、D析构函数调用顺序为?()

C c;

int main()
{
    A a;
    B b;
    static D d;
    return 0;
}

A. D   B   A   C

B. B   A   D   C

C. C   D   B   A

D. A   B   D   C

E. C   A   B   D

F. C   D   A   B

答案:E、B

解析:构造函数调用顺序:C是全局类,因此程序在运行时肯定是最先调用C的构造函数;随后从上到下依次调用A、B、D的构造函数,故顺序为C-->A-->B-->D。析构函数调用顺序:析构函数调用看的是类的生命周期,A和B都是局部对象,因此生命周期仅存在于当前函数,当函数结束时,最先调用B的析构函数(因为需要满足后构造的先析构),然后是A。虽然D和C的生命周期相同,但由于D后于C构造,因此D先调用析构函数,最后是C,故顺序为B-->A-->D-->C。

4. 友元

 友元提供了一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到⼀个类的里面。

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

 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。

 ⼀个函数可以是多个类的友元函数。

 友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。

 友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。

 友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是B的友元。

 有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

  友元函数声明:

  友元类声明:

class Date
{
public:
    //友元函数声明
    friend void Printf(const Date& d);
    //友元类声明
    friend class Time;
    Date(int year, int month, int day)
        //初始化列表
        :_year(year)
        , _month(month)
        , _day(day)
    {}
    int GetTimeHour()
    {
    }
    void Printf()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

class Time
{
public:
    Time(int hour, int minute, int second)
    {
        _hour = hour;
        _minute = minute;
        _second = second;
    }
    int GetDateYear(const Date& d)
    {
        return d._year;
    }
    int GetDateMonth(const Date& d)
    {
        return d._month;
    }
    int GetDateDay(const Date& d)
    {
        return d._day;
    }
private:
    int _hour;
    int _minute;
    int _second;
};


void Printf(const Date& d)
{
    cout << d._year << "/" << d._month << "/" << d._day << endl;
}


int main()
{
    Date d1(2024, 7, 14);
    Time t1(1, 1, 1);
    cout << t1.GetDateYear(d1) << "/" << t1.GetDateMonth(d1) << "/" << t1.GetDateDay(d1) << endl;
    d1.Printf();
    Printf(d1);
    return 0;
}

5. 内部类

 如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。

 内部类默认是外部类的友元类。

 内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使用,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。

  例如上面的练习题我们还可以写成:

#include <valarray>
class Solution {
public:

//内部类
class SL
{
public:
    SL()
    {
        x+=y;
        y++;
    }
};
    int Sum_Solution(int n) 
    {
        SL arr[n];
        return x;
    }
private:
    static int x;
    static int y;
};

int Solution::x = 0;
int Solution::y = 1;

  这里的SL就是内部类。因为内部类默认是外部类的友元函数,因此我们就可以将static变量写在Solution类中,SL函数中一样可以调用。

                                           创作不易,点个赞呗,蟹蟹啦~

相关推荐

  1. c++知识——对象

    2024-07-15 00:02:03       24 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-15 00:02:03       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-15 00:02:03       71 阅读
  3. 在Django里面运行非项目文件

    2024-07-15 00:02:03       58 阅读
  4. Python语言-面向对象

    2024-07-15 00:02:03       69 阅读

热门阅读

  1. E12.【C语言】练习:求两个数的最大公约数

    2024-07-15 00:02:03       23 阅读
  2. 分析 Android 应用中的日志信息应遵循的原则

    2024-07-15 00:02:03       20 阅读
  3. 牛客周赛51 F(静态区间最大连续子段和)

    2024-07-15 00:02:03       21 阅读
  4. 解锁Postman的API参数化:动态请求的秘诀

    2024-07-15 00:02:03       21 阅读
  5. 如何理解electron 的预加载脚本

    2024-07-15 00:02:03       20 阅读
  6. 力扣题解(回文子串)

    2024-07-15 00:02:03       21 阅读
  7. 题解:P9999 [Ynoi2000] tmostnrq

    2024-07-15 00:02:03       20 阅读