C++初阶---类和对象

news2024/9/24 13:22:58

目录

1. 类的引入

2. 类的定义

4. 类的访问限定符及封装

4.1 访问限定符

4.2 封装

5. 类的作用域

6.类的实例化

7.类对象模型

8. this指针

8.1 this指针的引出

8.2 this指针的特性

8.3 C语言和C++实现栈的对比

9.类的六个默认成员函数

10,构造函数

10.1 概念

10.2 特性

10.2 构造函数整体赋值

10.3 初始化列表

10.4 explicit关键字

11. 析构函数

11.1 概念

11.2 特性

12.拷贝构造函数

12.1 概念

12.2 特征

13.赋值运算符重载

13.1 运算符重载

13.2 赋值运算符重载

13.3 前置++和后置++重载

14. const 成员和static成员

14.1 const成员

14.2 static 成员

15. 友元函数和友元类

15.1 友元函数

15.2 友元类

16 内部类


1. 类的引入

        C语言结构体中只能定义变量,而在C++中,结构体不仅仅可以定义变量,还可以定义函数。但是相对于对结构体的定义在C语言中使用struct,而在C++中更喜欢使用class代替,而使用了c++方式来定义结构体,就会发现一些简单的结构,如栈,队列等,在实现后会使用起来会方便很多。如栈的实现:

// C++实现
typedef int DataType;
struct Stack {
    void Init(size_t capacity) {
        _array = (DataType*)malloc(sizeof(DataType) * capacity);
        if (nullptr == _array) {
            perror("malloc申请空间失败");
            return;
        }
        _capacity = capacity;
        _size = 0;
    }
    void Push(const DataType& data) {
        // 扩容
        _array[_size] = data;
        ++_size;
    }
    DataType Top() { return _array[_size - 1]; }
    void Destroy() {
        if (_array) {
            free(_array);
            _array = nullptr;
            _capacity = 0;
            _size = 0;
        }
    }
    DataType* _array;
    size_t _capacity;
    size_t _size;
};
int main() {
    Stack s;
    s.Init(10);
    s.Push(1);
    s.Push(2);
    s.Push(3);
    cout << s.Top() << endl;
    s.Destroy();
    return 0;
}

2. 类的定义

class classname{

        类体;由成员函数和成员变量组成

};

        其中class为定义类的关键字,classname为类的名称,{ }中为类的主体,注意类定义结束时后边的分号不能省略。

        类体中的内容称为类的成员,类中的变量称为成员变量,类中的方法和函数称为类的方法或者成员函数。

 类的两种定义方法:

        1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。

        2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::

4. 类的访问限定符及封装

4.1 访问限定符

        C++实现封装的方式:用类和对象的属性与方法结合在一起,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。

        

【访问限定说明】

        1. public修饰的成员在类外可以直接被访问

        2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)

        3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止

        4. 如果后面没有访问限定符,作用域就到 } 即类结束。

        5. class的默认访问权限为private,struct为public(因为struct要兼容C)

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。

C++中struct和class的区别是什么?

        C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来 定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类 默认访问权限是private。

4.2 封装

       面向对象的三大特性:封装,继承,多态。

       封装:将数据和操作数据的方法有机的结合,隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互。

        封装本质上是一种管理,让用户更加方便的使用类。在c++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机的结和,通过访问权限来隐藏对象内部的实现细节,控制哪些方法可以在类外部直接被使用。

5. 类的作用域

        类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 : : 作用域操作符来指明成员属于哪一个类域。

classs student{
public:
    void PrintPersonInfo();
private:
    char name[10];
    char gender[3];
    int age;
};
//此处指明PrintPersonInfo是属于student这个作用域
void student::PrintPersonInfo(){


}

6.类的实例化

用类类型来创建面向对象的过程称为类的实例化。

        1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它,

        2. 一个类可以实例化多个对象,实例化出来的对象,占实际的物理空间,存储类成员变量。

  

7.类对象模型

        一个类的大小,实际上就是该类中成员变量之和,要注意内存对齐,注意空类的大小,空类比较特使,编译器给空类一个字节来唯一标识这个类的对象。

结构体内存对齐规则:

        1. 第一个成员在与结构体偏移量为0的地址处。

        2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8

        3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。

        4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

常见问题:

1. 结构体怎么对齐? 为什么要进行内存对齐?

        结构体对齐是指编译器在安排结构体中的各个成员时,为了提高访问速度和节省内存,按照特定规则将结构体成员排列在一定的地址上。内存对齐是为了提高访问速度,因为大多数计算机体系结构要求对象的地址符合某些特定的对齐要求,否则会导致性能下降或者错误。

2. 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?

        可以使用编译器提供的特定指令或者属性来指定结构体的对齐方式,不同的编译器可能有不同的语法。一般来说,可以使用 #pragma pack(n) 或者 __attribute__((aligned(n))) 等指令来实现对齐。对于任意字节对齐,一些编译器可能支持,但并非所有都支持,具体取决于编译器的实现。

3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景

        大小端是指在存储多字节数据时,低地址端存放最低有效字节的方式。小端模式是指数据的低字节保存在内存的低地址处,大端模式是指数据的高字节保存在内存的低地址处。你可以通过写一个简单的程序来测试机器的大小端模式,比如定义一个整型变量,然后将其转换为字节序列,然后查看字节序列的顺序即可确定是大端还是小端。

#include<stdio.h>
int main(){
    unsigned int num = 1;
    char *ptr = (char *)&num;
    
    if (*ptr == 1) {
        printf("小端\n");
    } else {
        printf("大端\n");
    }
    
    return 0;
}

8. this指针

8.1 this指针的引出

        为了很好的了解this指针,我们来通过定义一个日期类,并通过这个日期类来对this指针进行一个理解。

class Date
{ 
public:
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout <<_year<< "-" <<_month << "-"<< _day <<endl;
    }
private:
    int _year;     // 年
    int _month;    // 月
    int _day;      // 日
};
int main()
{
    Date d1, d2;
    d1.Init(2022,1,11);
    d2.Init(2022, 1, 12);
    d1.Print();
    d2.Print();
    return 0;
}

对于上述日期类:Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函 数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?         C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏 的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编 译器自动完成

8.2 this指针的特性

        1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。

        2. 只能在“成员函数”的内部使用

        3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针。

        4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

8.3 C语言和C++实现栈的对比

        C语言实现

typedef int DataType;
typedef struct Stack {
    DataType* array;
    int capacity;
    int size;
} Stack;
void StackInit(Stack* ps) {
    assert(ps);
    ps->array = (DataType*)malloc(sizeof(DataType) * 3);
    if (NULL == ps->array) {
        assert(0);
        return;
    }
    ps->capacity = 3;
    ps->size = 0;
}
void StackDestroy(Stack* ps) {
    assert(ps);
    if (ps->array) {
        free(ps->array);
        ps->array = NULL;
        ps->capacity = 0;
        ps->size = 0;
    }
}
void CheckCapacity(Stack* ps) {
    if (ps->size == ps->capacity) {
        int newcapacity = ps->capacity * 2;
        DataType* temp =
            (DataType*)realloc(ps->array, newcapacity * sizeof(DataType));
        if (temp == NULL) {
            perror("realloc申请空间失败!!!");
            return;
        }
        ps->array = temp;
        ps->capacity = newcapacity;
    }
}
void StackPush(Stack* ps, DataType data) {
    assert(ps);
    CheckCapacity(ps);
    ps->array[ps->size] = data;
    ps->size++;
}
int StackEmpty(Stack* ps) {
    assert(ps);
    return 0 == ps->size;
}
void StackPop(Stack* ps) {
    if (StackEmpty(ps))
        return;
    ps->size--;
}
DataType StackTop(Stack* ps) {
    assert(!StackEmpty(ps));
    return ps->array[ps->size - 1];
}
int StackSize(Stack* ps) {
    assert(ps);
    return ps->size;
}
int main() {
    Stack s;
    StackInit(&s);
    StackPush(&s, 1);
    StackPush(&s, 2);
    StackPush(&s, 3);
    StackPush(&s, 4);
    printf("%d\n", StackTop(&s));
    printf("%d\n", StackSize(&s));
    StackPop(&s);
    StackPop(&s);
    printf("%d\n", StackTop(&s));
    printf("%d\n", StackSize(&s));
    StackDestroy(&s);
    return 0;
}

C++实现

typedef int DataType;
class Stack {
public:
    void Init() {
        _array = (DataType*)malloc(sizeof(DataType) * 3);
        if (NULL == _array) {
            perror("malloc申请空间失败!!!");
            return;
        }
        _capacity = 3;
        _size = 0;
    }
    void Push(DataType data) {
        CheckCapacity();
        _array[_size] = data;
        _size++;
    }
    void Pop() {
        if (Empty())
            return;
        _size--;
    }
    DataType Top() { return _array[_size - 1]; }
    int Empty() { return 0 == _size; }
    int Size() { return _size; }
    void Destroy() {
        if (_array) {
            free(_array);
            _array = NULL;
            _capacity = 0;
            _size = 0;
        }
    }

private:
    void CheckCapacity() {
        if (_size == _capacity) {
            int newcapacity = _capacity * 2;
            DataType* temp =
                (DataType*)realloc(_array, newcapacity * sizeof(DataType));
            if (temp == NULL) {
                perror("realloc申请空间失败!!!");
                return;
            }
            _array = temp;
            _capacity = newcapacity;
        }
    }

private:
    DataType* _array;
    int _capacity;
    int _size;
};
int main() {
    Stack s;
    s.Init();
    s.Push(1);
    s.Push(2);
    s.Push(3);
    s.Push(4);

    printf("%d\n", s.Top());
    printf("%d\n", s.Size());
    s.Pop();
    s.Pop();
    printf("%d\n", s.Top());
    printf("%d\n", s.Size());
    s.Destroy();
    return 0;
}

两者对比可以看出:

  •         在用C语言实现时,Stack相关操作函数有以下共性:
  •         每个函数的第一个参数都是Stack*
  •         函数中必须要对第一个参数检测,因为该参数可能会为NULL
  •         函数中都是通过Stack*参数操作栈的 调用时必须传递Stack结构体变量的地址

        结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即数据和操作数据 的方式是分离开的,而且实现上相当复杂一点,涉及到大量指针操作,稍不注意可能就会出错。

        C++中通过类可以将数据 以及 操作数据的方法进行完美结合,通过访问权限可以控制那些方法在 类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。 而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,即C++中 Stack * 参数是编译器维护的,C语言中需用用户自己维护。

9.类的六个默认成员函数

        一般来说如果一个类中什么成员都没有,即空类。生成空类,虽然其中什么成员都没有,但是会默认生成六个成员函数:

  1.         初始化和清理(构造函数和析构函数,其中构造函数主要完成初始化,析构函数完成清理工作),
  2.         拷贝复制(拷贝构造和赋值重载,拷贝构造使用同类对象初始化创建对象,赋值重载把一个对象赋值给另一个对象),
  3.         取地址重载(普通对象和const对象取地址)//一般不需要重新定义,编译器会默认生成,只有特殊情况才需要重载

10,构造函数

10.1 概念

      构造函数是一个特殊的成员函数,名字和类名相同,创建类类型对象时由编译器自动调用,以保证每个数据都有一个自己合适的初始化值,并且在对象整个生命周期只调用一次。

10.2 特性

        构造函数是一个特殊的成员函数吗,虽然名称叫做构造,但是主要任务并不是开空间创建对象,而是初始化对象。

具有如下特征:

  1. 函数名与类名相同
  2. 无返回值
  3. 对象实例化时编译器制动调用相应的构造函数
  4. 构造函数可以重载
  5. 如果类中没有显式定义构造函数,则编译器会自动生成一个无参的默认构造函数,一旦用户显式定义就不会生成
  6. 无参构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

编译器生成默认构造函数的作用:c++把类型分为内置类型和自定义类型,内置类型就是语言提供的数据类型,自定义类型就是我们自己定义的类型,当我们使用自定义类型的时候,编译器就会调用这个自定义类型成员调用它的默认成员函数,从而来对自定义类型进行初始化。在C++11中针对内置类型不初始化的缺陷打了补丁,内置类型成员变量在类中声明式可以给默认值。

10.2 构造函数整体赋值

        在创建对象时,编译器通过调用构造函数,给对象中的各个成员变量一个合适的初始值。如上述的日期类,但是调用构造函数后,对象中已经有一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称为初始化,因为初始化只能初始化一次,而构造体函数内可以多次赋值。

10.3 初始化列表

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

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.类中包含引用成员变量,const成员变量,自定义类型成员变量,必须放在初始化列表位置进行初始化。

        3.尽量使用初始化列表进行初始化,对于自定义类型成员变量,一定会先试用初始化列表进行初始化。

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

10.4 explicit关键字

        构造函数不仅可以构造与初始化对象,对于接收单个参数的构造函数,具有类型转换的作用,接收单个参数的构造函数具体表现:

        1.构造函数只有一个参数

        2.构造函数有多个参数,除第一个参数没有默认值,其他参数都有默认值

        3.全缺省的构造函数

class Date {
public:
    // 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
    // explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译
    explicit Date(int year) : _year(year) {}
    /*
    // 2.
   虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具有类型转
   换作用
    // explicit修饰构造函数,禁止类型转换
    explicit Date(int year, int month = 1, int day = 1)
    : _year(year)
    , _month(month)
    , _day(day)
    {}
    */
    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(2022);
    // 用一个整形变量给日期类型对象赋值
    // 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值
    d1 = 2023;
    // 将1屏蔽掉,2放开时则编译失败,因为explicit修饰构造函数,禁止了单参构造函数类型转换的作用
}

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

11. 析构函数

11.1 概念

        析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是有编译器完成的,对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。、

11.2 特性

        1.析构函数名是在类名前加一个字符 ~

        2.无参数返回值类型

        3.一个类只能有一个析构函数,如果未显式定义则会默认生成,析构函数不可以重载。

        4.对象生命周期结束时,编译系统就会自动调用析构函数。

        5.如果类中没有申请资源,析构函数可以不写,直接使用编译器默认生成的析构函数,但是如果有空间的申请一定需要写析构函数,否则会导致内存资源的泄露。

class Time {
public:
    ~Time() { cout << "~Time()" << endl; }

private:
    int _hour;
    int _minute;
    int _second;
};
class Date {
private:
    // 基本类型(内置类型)
    int _year = 1970;
    int _month = 1;
    int _day = 1;
    // 自定义类型
    Time _t;
};
int main() {
    Date d;
    return 0;
}
// 程序运行结束后输出:~Time()
// 在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month,
// _day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;
// 而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:
// main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date
// 类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部
// 调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁main函数中并没有直接调用Time类析构函数,
// 而是显式调用编译器为Date类生成的默认析构函数
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

12.拷贝构造函数

12.1 概念

        拷贝构造函数只有单个形参,该形参是对本类型对象的引用(一般使用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用

12.2 特征

        1.拷贝构造函数是构造函数的一个重载形式

        2.拷贝构造函数的参数只有一个且必须是类对象的引用,使用传值方式编译器会直接报错,会引发无穷递归调用,

        3.若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

        4.编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝

                注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

        5. 拷贝构造函数典型调用场景:

  •                 使用已存在对象创建新对象
  •                 函数参数类型为类类型对象
  •                 函数返回值类型为类类型对象

        为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用 尽量使用引用。

13.赋值运算符重载

13.1 运算符重载

        为了增强代码的可读性,C++引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名字以及参数列表,其返回值类型和普通函数类似。

        函数名称为:关键字operator后边接需要重载的运算符符号。

        函数原型:返回值类型operator操作符(参数列表)

注意:

         不能通过连接其他符号来创建新的操作符:比如operator@

        重载操作符必须有一个类类型参数

        用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义

        作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this

        .*   ::   sizeof   ?   : . 注意以上5个运算符不能重载。

13.2 赋值运算符重载

1. 赋值运算符重载格式

  •         参数类型:const T&,传递引用可以提高传参效率
  •         返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  •         检测是否自己给自己赋值
  •         返回*this :要复合连续赋值的含义
class Date {
public:
    Date(int year = 1900, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }

    Date(const Date& d) {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

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

2. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注 意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符 重载完成赋值。

13.3 前置++和后置++重载

class Date {
public:
    Date(int year = 1900, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }
    // 前置++:返回+1之后的结果
    // 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
    Date& operator++() {
        _day += 1;
        return *this;
    }
    // 后置++:
    // 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
    // C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
    自动传递
    // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
    一份,然后给this + 1
        //       而temp是临时对象,因此只能以值的方式返回,不能返回引用
        Date
        operator++(int) {
        Date temp(*this);
        _day += 1;
        return temp;
    }

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

14. const 成员和static成员

14.1 const成员

        将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数 隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

1. const对象可以调用非const成员函数吗? 2. 非const对象可以调用const成员函数吗? 3. const成员函数内可以调用其它的非const成员函数吗? 4. 非const成员函数内可以调用其它的const成员函数吗?

解答:

  1. const 对象可以调用非 const 成员函数,但是在编译过程中会发出警告。因为 const 对象调用非 const 成员函数可能会导致对象状态的改变,违反了 const 对象的特性。

  2. 非 const 对象可以调用 const 成员函数,这是允许的。因为 const 成员函数不会修改对象的状态,所以非 const 对象调用 const 成员函数是安全的。

  3. const 成员函数内可以调用其他的非 const 成员函数,但是这样做会使得整个对象在调用期间被视为非 const。这是因为 const 成员函数虽然不能修改对象的成员变量,但是可以修改对象的 mutable 成员变量或者调用其他非 const 成员函数来修改对象的状态。

  4. 非 const 成员函数内可以调用 const 成员函数,这是完全合法的。因为非 const 成员函数可以修改对象的状态,包括调用其他 const 成员函数。调用 const 成员函数不会改变对象的状态,所以在非 const 成员函数内调用 const 成员函数是安全的。

14.2 static 成员

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

特性:

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

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

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

        4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员

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

问题及解答:

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

                静态成员函数可以调用非静态成员函数,但是需要通过对象来调用非静态成员函数。因为静态成员函数没有 this 指针,无法直接访问非静态成员函数,所以需要通过对象来调用。

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

                非静态成员函数可以直接调用类的静态成员函数,不需要通过对象来调用。静态成员函数属于整个类,而不是某个特定对象,因此在非静态成员函数中可以直接访问类的静态成员函数。

        3.实现一个类,计算程序中创建出了多少个类对象(编程)

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;
void TestA() {
    cout << A::GetACount() << endl;
    A a1, a2;
    A a3(a1);
    cout << A::GetACount() << endl;
}

15. 友元函数和友元类

15.1 友元函数

        友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声 明,声明时需要加friend关键字。

class Date {
    friend ostream& operator<<(ostream& _cout, const Date& d);
    friend istream& operator>>(istream& _cin, Date& d);

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

private:
    int _year;
    int _month;
    int _day;
};
ostream& operator<<(ostream& _cout, const Date& d) {
    _cout << d._year << "-" << d._month << "-" << d._day;
    return _cout;
}
istream& operator>>(istream& _cin, Date& d) {
    _cin >> d._year;
    _cin >> d._month;
    _cin >> d._day;
    return _cin;
}
int main() {
    Date d;
    cin >> d;
    cout << d << endl;
    return 0;
}

说明:

  •         友元函数可访问类的私有和保护成员,但不是类的成员函数
  •         友元函数不能用const修饰
  •         友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  •         一个函数可以是多个类的友元函数
  •         友元函数的调用与普通函数的调用原理相同

15.2 友元类

        友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

  •         友元关系是单向的,不具有交换性。 比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time 类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
  •         友元关系不能传递 如果B是A的友元,C是B的友元,则不能说明C时A的友元。
  •         友元关系不能继承,

16 内部类

        概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外 部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

        注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中 的所有成员。但是外部类不是内部类的友元。

        特性:

  1.         内部类可以定义在外部类的public、protected、private都是可以的。
  2.         注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  3.         sizeof(外部类)=外部类,和内部类没有任何关系。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1539633.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Penpad 生态资产 $PDD LaunchPad 在即,Season 2 规则解读

Penpad是Scroll上的LauncPad平台&#xff0c;该平台继承了Scroll底层的技术优势&#xff0c;并基于零知识证明技术&#xff0c;推出了系列功能包括账户抽象化、灵活的挖矿功能&#xff0c;并将在未来实现合规为RWA等资产登录Scroll生态构建基础。该平台被认为是绝大多数项目、资…

IM系统设计之消息存储

IM系统设计之消息存储 项目地址&#xff1a;gitgithub.com:muyixiaoxi/Link.git 消息存储结构 消息存储结构如下图所示 当用户A向用户B发送一条消息时 将消息发送给 serverserver 将消息进行持久化判断用户B是否在线&#xff0c;如果在线直接转发&#xff1b;如果离线&am…

蓝桥杯-单片机基础8——上下位机的串口通信设置(附小蜜蜂课程代码)

蓝桥杯单片机组备赛指南请查看这篇文章&#xff1a;戳此跳转蓝桥杯备赛指南文章 本文章针对蓝桥杯-单片机组比赛开发板所写&#xff0c;代码可直接在比赛开发板上使用。 型号&#xff1a;国信天长4T开发板&#xff08;绿板&#xff09;&#xff0c;芯片&#xff1a;IAP15F2K6…

经得住拷问的HTTPS原理解析

此文涵盖的大致内容&#xff1a; 理解HTTPS原理的概念什么是对称加密和非对称加密&#xff1f;什么是数字签名&#xff1f;怎么生成&#xff1f;怎么校验&#xff1f;啥时候是对称加密&#xff1f;啥时候是非对称加密&#xff1f;啥时候进行算法加密&#xff1f;什么算法&…

【SpringCloud】Consul中数据持久化配置并注册为Windows服务

Consul用起来两个比较麻烦的地方: 一是每次都需要执行打开命令行执行 consul agent -dev 命令来启动Consul服务,而且不能关掉命令行.另外一点就是在Consul中设置的数据,每次重启之后就没了. 就很烦.因此为大家带来解决上述问题的方法 首先要在官网下载Consul安装包的解压目录下…

每日一题 --- 设计链表[力扣][Go]

设计链表 题目&#xff1a;707. 设计链表 你可以选择使用单链表或者双链表&#xff0c;设计并实现自己的链表。 单链表中的节点应该具备两个属性&#xff1a;val 和 next 。val 是当前节点的值&#xff0c;next 是指向下一个节点的指针/引用。 如果是双向链表&#xff0c;则…

C++生成动态连接库

文章目录 一、静态连接与动态连接二、 动态链接库&#xff08;DLL&#xff09;的创建三、dll库的使用四、 动态链接链接库工作原理五、extern "C" 一、静态连接与动态连接 静态库和动态库区别是库的加载时间不同。静态库&#xff1a;在链接阶段库将会与.o目标文件一起…

【redis】服务器架构演进

架构演进 单机架构应用数据分离架构应⽤服务集群架构读写分离 / 主从分离架构冷热分离架构垂直分库微服务架构 单机架构 所有的应用服务、业务所需的数据、业务处理等都在一台服务器上。 在初期&#xff0c;用户访问量很少&#xff0c;对服务器的的性能和安全没有很高的要求&am…

docker入门(一)—— docker概述

docker 概述 docker 官网&#xff1a;http://www.docker.com 官网文档&#xff1a; https://docs.docker.com/get-docker/ Docker Hub官网&#xff1a;https://hub.docker.com &#xff08;仓库&#xff09; 什么是 docker docker 是一个开源的容器化平台&#xff0c;可以…

盛最多水的容器——算法思路

题目链接&#xff1a;11. 盛最多水的容器 - 力扣&#xff08;LeetCode&#xff09; 题解&#xff1a; while中的主要步骤解释&#xff1a; 首先判断索引 i 处的高度是否小于索引 j 处的高度&#xff0c; 如果小于则计算 ( j - i ) * height [ i ]&#xff1b;&#xff08;先取…

有哪些强大好用的AI表格数据处理工具或者 AI Excel工具?

在繁忙的工作和生活中&#xff0c;处理大量的表格数据往往令人感到头疼。面对一列列数字、一行行文字&#xff0c;我们需要花费大量的时间和精力去整理、核对。然而&#xff0c;随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;技术正逐渐改变这一现状。 如…

C# 对App.config、Web.config的appSettings节点数据进行加密

appSettings加密原因&#xff0c;就是因为容易暴露服务器账号和密码&#xff0c;而且客户也不允许 使用ASP.NET提供的命令工具aspnet_regiis来创建加密命令&#xff1b;aspnet_regiis是提供了直接对配置文件加密的功能的&#xff1b;并且使用aspnet_regiis加密的配置节点在读取…

uniapp套壳打包成apk

不管是vue项目还是uniapp项目,只要能打包成 index.html都可以通过uniapp打包成apk 1.首先把这个项目发布到线上 拿百度举例: 发布到百度服务器用 www.baidu.com能访问到 2.然后到uniapp上发布项目用默认的 vue2,如果你的项目是vue3也用vue2 3.废话我觉得说太多了,直接…

被群辉升级折腾得够呛

群辉升级后&#xff0c;因为版权问题&#xff0c;VideoStation 不支持播放 EAC3 格式的视频了&#xff0c;导致我大量视频都没法用 AppleTV 播放。 网上早期流传了各种方案&#xff0c;比如回退 VideoStation 版本等&#xff0c;都试过了&#xff0c;没啥用。 2024 年 3 月&a…

基于springboot+vue的影院订票系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

不要再封装各种 Util 工具类了,神级框架值Hutool

Hutool 谐音 “糊涂”&#xff0c;寓意追求 “万事都作糊涂观&#xff0c;无所谓失&#xff0c;无所谓得” 的境界。 Hutool 是一个 Java 工具包&#xff0c;也只是一个工具包&#xff0c;它帮助我们简化每一行代码&#xff0c;减少每一个方法&#xff0c;让 Java 语言也可以 …

深入理解Python异常处理机制:助力你的自动化测试脚本

前言 前些天&#xff0c;公司准备使用开源BI工具superset&#xff0c;但部署成功后&#xff0c;连接阿里数仓获取表时&#xff0c;一直报错&#xff0c;苦于日志不详细&#xff0c;从日志中并没有看出哪里的问题&#xff0c;然后就拉源码进行调试&#xff0c;终于找到抛出异常的…

2024年计算机三级|数据库习题整理(自用③)

所有题目均来自【三级数据库技术基础题库】&#xff0c;此博客仅包含部分设计题与应用题&#xff0c;用于自主的回顾学习&#xff0c;仅供参考。 ER图绘制 代码补全 方案选择 &#xff08;1&#xff09;在不改变SQL语句和不对表做分区的情况下&#xff0c;可以为学生进出校的申…

自动驾驶感知新范式——BEV感知经典论文总结和对比(一)

自动驾驶感知新范式——BEV感知经典论文总结和对比&#xff08;一&#xff09; 博主之前的博客大多围绕自动驾驶视觉感知中的视觉深度估计&#xff08;depth estimation&#xff09;展开&#xff0c;包括单目针孔、单目鱼眼、环视针孔、环视鱼眼等&#xff0c;目标是只依赖于视…

【Python爬虫】将某网页中表格里的十六进制颜色值转换成十进制,再生成新表格

【需求】 在 https://www.cnblogs.com/heyang78/p/5712076.html 上有360种颜色及代码&#xff0c;但很遗憾没有十进制的RGB值&#xff0c;使用时需要自己转换一下&#xff0c;此过程依赖网络或计算器&#xff0c;颇为不便。因此&#xff0c;拟设计一爬虫将原有表格内容取出&am…