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

目录

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是临时对象,因此只能以值的方式返回,不能返回引用Dateoperator++(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.mzph.cn/news/766152.shtml

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

相关文章

新能源汽车BMS应用设计

新能源汽车BMS应用设计 电池管理系统&#xff08;BMS&#xff09; 概述 电池管理系统&#xff08;BMS&#xff09;为一套保护动力电池使用安全的控制系统&#xff0c;时刻监控电池的使用状态&#xff0c;通过必要措施缓解电池组的不一致性&#xff0c;为新能源车辆的使用安全…

Prometheus+Grafana 监控Tongweb嵌入式(by lqw)

文章目录 1.思路2.部署准备3.Grafana仪表盘json文件下载4.tw嵌入式jar包本地引入依赖并测试运行5.运行jmx_prometheus_javaagent-0.19.0.jar形式获取监控数据&#xff08;方法一&#xff09;6.使用Actuator 获取监听数据&#xff08;方法二&#xff09;7.Prometheus部署8.Prome…

代码随想录day29(2)二叉树:将有序数组转换为二叉搜索树(leetcode108)

题目要求&#xff1a;将一个按照升序排列的有序数组&#xff0c;转换为一棵高度平衡二叉搜索树。 思路&#xff1a;思路比较简单&#xff0c;如果目标是平衡二叉树&#xff0c;我们每次只需要取数组的中间元素作为根节点&#xff0c;分成左右两个子树&#xff0c;再递归地进行…

B站python爬虫课程笔记(Q16-19结束)

下面是学习的网址&#xff1a; ​​​​​​【Python爬虫】 目录 16、捕捉异常try&except语句的一些问题 1&#xff09;一些常见的异常类型 2&#xff09;try&except的使用 17、测试Bug的一些问题 1&#xff09;assert断定函数的使用 2&#xff09;unittest单元…

学生综合考评管理系统|jsp+ Mysql+Java+ (可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;…

微服务鉴权的几种实现方案

1.Token 1.1 Token透传&#xff08;不推荐&#xff09; 刚开始接触微服务时网上给的方案大都数是通过透传Token做鉴权&#xff0c;但我认为这种方式不是很妥当。接着往下看&#xff1a; 这种方式通过透传Token使得各微服务都能获取到当前登录人信息&#xff0c;在代码编写上确…

RK3568平台 网络唤醒

一.什么是网络唤醒 网络唤醒(Wake-on-LAN&#xff0c;WOL)是一种计算机局域网唤醒技术&#xff0c;使局域网内处于关机或休眠状态的计算机&#xff0c;将状态转换成引导(Boot Loader)或运行状态。无线唤醒(Wake-on-Wireless-LAN&#xff0c;WoWLAN)作为 WOL 的补充技术&#x…

SpringBoot整合MyBatisPlus实现增删改查

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉🍎个人主页:Leo的博客 💞当前专栏: 循序渐进学SpringBoot ✨特色专栏: MySQL学习 🥭本文内容:SpringBoot整合MyBatisPlus实现增删改查 📚个人知识库: Leo知识库,欢迎大家…

【QT入门】 Qt实现自定义信号

往期回顾&#xff1a; 【QT入门】图片查看软件(优化)-CSDN博客 【QT入门】 lambda表达式(函数)详解-CSDN博客 【QT入门】 Qt槽函数五种常用写法介绍-CSDN博客 【QT入门】 Qt实现自定义信号 一、为什么需要自定义信号 比如说现在一个小需求&#xff0c;我们想要实现跨ui通信&a…

优化选址问题 | 基于鹈鹕算法求解基站选址问题含Matlab源码

目录 问题代码问题 鹈鹕算法(Pelican Optimization Algorithm, POA)是一种相对较新的启发式优化算法,模拟了鹈鹕鸟觅食的行为。这种算法通常用于解决复杂的优化问题,如函数优化、路径规划、调度问题等。基站选址问题通常是一个复杂的优化问题,需要考虑覆盖范围、干扰、成…

阿里云ECS服务器u1通用算力型CPU性能如何?

阿里云服务器u1是通用算力型云服务器&#xff0c;CPU采用2.5 GHz主频的Intel(R) Xeon(R) Platinum处理器&#xff0c;通用算力型u1云服务器不适用于游戏和高频交易等需要极致性能的应用场景及对业务性能一致性有强诉求的应用场景(比如业务HA场景主备机需要性能一致)&#xff0c…

LAMP架构与搭建论坛

目录 1、LAMP架构简述 2、各组件作用 3、构建LAMP平台 3.1编译安装Apache httpd服务 3.1.1关闭防火墙&#xff0c;将安装Apache所需软件包传到/opt目录下 3.1.2安装环境依赖包 3.1.3配置 设置安装目录、安装模块 3.1.4编译安装 3.1.5优化配置文件路径 3.1.6启动apache…

北航计算机软件技术基础课程作业笔记【1】

为白成刚老师的课程&#xff0c;简单做一个记录&#xff0c;内容偏基础&#xff0c;自己仅保留认为有用的部分 L1&#xff1a;算法概论 课程简单介绍了复杂度、算法的概念 1.作业 计算下列各片断程序中xx1的执行次数 (1) for (i1; i<n; i)for (j1; i<n; j)for (k1; …

牛客小白月赛89补题1(ABCD)(偏难)

评价&#xff1a; 高情商&#xff1a;收获很大 &#xff0c;让自己进一步认清自己。 低情商&#xff1a;题目难&#xff0c;自己太菜了。 今天还有一些其他事&#xff0c;剩下的题明天再补。 我们从a题开始吧&#xff1a; A.签到 我们只要看看其中的max与min是否不符合即可…

Android14之selinux报错:ERROR: end of file in comment(一百九十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

项目成功秘诀:高效管理策略确保按时交付v2

一、项目成功的重要性 在当今竞争激烈的商业环境中&#xff0c;项目的成功对于企业的生存和发展具有至关重要的意义。项目的成功不仅意味着企业能够达成既定的业务目标&#xff0c;还能提升企业的市场地位、增强品牌影响力&#xff0c;并为企业的可持续发展奠定坚实基础。我们…

中型企业网络路由器配置(ensp)实验

vlan、vlan间路由、ospf协议等来实现三层交换机和单臂路由之间的通信 拓扑图&#xff1a; 1. 配置三层交换机vlan和vlan间路由 SW1 #进入视图 sys sysn sw1 undo info-center enable#配置vlan vlan batch 10 20 30 40 50 60#配置access口 int g0/0/1 port link-type access …

视觉信息处理和FPGA实现第6次作业-Matlab实现灰度图像的亮度调节

一、代码 close all;clear all;clc; pic imread("cameraman.tif"); [M,N] size(pic); disp("Contrast Ajust Demo"); value input("Please input number of value, range: 0~2\n"); while value>2 || value<0disp("The number is in…

python--for循环

for循环&#xff1a; python中的for循环是用来迭代容器中的每一个元素的&#xff0c;而不是c,java中理解那个循环&#xff1b; for 零时变量 in 容器&#xff1a; print&#xff08;零时变量&#xff09; #每一个迭代出的元素 range 全局函数&#xff1a; …