c++类和对象【初阶,建议电脑看,移动端目录折叠】含[类的定义,类的作用域,类的实例化,及补充具体用法,已经修正this指针的示例]


电脑端目录:

在这里插入图片描述

类与对象:

类出现的原因:

c语言是面向过程的语言,c++是面向对象的语言:面向过程编程更加关注数据和操作之间的线性关系,适合解决简单的任务;而面向对象编程更加关注对象之间的交互和关联,适合解决复杂的系统设计问题。

关于类:
代码组织和结构化:类提供了一种将数据和功能捆绑在一起的方式,使得代码更加组织化和易于管理。通过类,可以将相关的数据和方法封装在一起,形成逻辑上的单元,使得代码更具可读性和可维护性。
抽象和模块化:类提供了抽象的机制,使得程序员可以将现实世界中的对象抽象成代码中的类。这种抽象性使得程序更容易理解和设计。同时,类的模块化特性也使得代码更易于重用,可以在不同的地方调用同一个类来实现相似的功能。
封装和信息隐藏:类封装了数据和方法,使得对象的内部状态对外部是隐藏的,只提供有限的接口供外部访问。这种信息隐藏的特性提高了代码的安全性和可靠性,同时也降低了代码的耦合度。
继承和多态:类之间可以通过继承关系建立联系,子类可以继承父类的属性和方法,并且可以根据需要进行扩展和重写。这种继承机制使得代码的重用性更高,并且提供了多态性,即同一个方法可以根据对象的不同而表现出不同的行为。
抽象数据类型(ADT):类可以被看作是抽象数据类型的一种实现方式。通过类,可以定义抽象数据类型,并且在程序中使用这些类型来表示和操作数据,从而使得程序的设计更加抽象和灵活。

类的定义:关键字class

class className
 {
 // 类体:由成员函数和成员变量组成
};  // 一定要注意后面的分号

class为定义类的关键字ClassName为类的名字{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:我现在要讲的主要是成员变量成员函数,类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。

补充C语言结构体访问结构体内部成员

在C语言中,你可以使用结构体变量名后跟成员名的方式来访问结构体中的成员。这称为成员访问操作符"."下面是一个简单的示例:

#include <stdio.h>

// 定义一个结构体
struct House {
    char address[100];
    int rooms;
    double area;
};

int main() {
    // 创建一个 House 结构体变量
    struct House myHouse;

    // 设置结构体成员变量的值
    strcpy(myHouse.address, "123 Main St");
    myHouse.rooms = 3;
    myHouse.area = 150.5;

    // 输出结构体成员变量的值
    printf("Address: %s\n", myHouse.address);
    printf("Number of rooms: %d\n", myHouse.rooms);
    printf("Area: %.2f square meters\n", myHouse.area);

    return 0;
}

在 C 语言中,成员访问操作符 . 用于访问结构体或联合体的成员。这个操作符在结构体变量名后面加上成员名,以指示访问该结构体或联合体中特定成员的值。
在 C++ 中,类的成员变量可以使用成员访问操作符.来进行访问。

访问限定符:

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

在这里插入图片描述

  1. public修饰的成员在类外可以直接被访问
  2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  4. 如果后面没有访问限定符,作用域就到}即类结束。
  5. class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:

在 C++ 中,访问限定符是在编译时执行访问控制的。一旦代码被编译成可执行程序并加载到内存中,访问限定符就不再起作用。因此,一旦程序在运行时,无论是私有成员、受保护成员还是公共成员,对于程序中的其他部分都是一样可见的。
这意味着,在运行时对于类的对象而言,它们的私有、受保护和公共成员都可以被访问,没有任何区别。 访问限定符的目的是在编译时强制执行访问控制规则,以确保程序员遵循类设计的封装原则,但一旦程序被编译为机器代码并运行,这些限定符不再有任何效果。

用例:
#include <iostream>

class MyClass {
private:
    int privateMember; // 私有成员变量
protected:
    int protectedMember; // 受保护成员变量
public:
    int publicMember; // 公共成员变量
};

类的作用域:

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

#include <stdio.h>
#include<iostream>
using namespace std;
class Person
{
public:
    void PrintPersonInfo();
private:
    char _name[20];
    char _gender[3];
    int  _age;
};

void Person::PrintPersonInfo()
{
    cout << _name << " " << _gender << " " << _age << endl;
}
int main(){
    
}

在 C++ 中,类的成员函数可以访问该类的私有成员变量(类的成员函数是类的一部分,它们被视为类的内部。) Person::PrintPersonInfo() 函数可以访问 _name、_gender 和 _age 这些私有成员变量。这是因为该函数是 Person 类的成员函数,因此它有权访问该类的所有成员,包括私有成员。

类的实例化:

类的实例化是指创建类的对象的过程。

简单来说,定义一个类就像是设计了一个蓝图描述了对象的特征和行为,但并没有真正创建对象。只有当类的实例(对象)被创建时,**系统才会为其分配内存空间,**根据类的定义构造对象,并且这个对象才能够真正存在于内存中。 实现操作跟结构体的操作一致

class treenode{
};
int main(){
	treenode s1;//也可以class treenode s1;
	//此时的s1就是实例化的对象
}

一个类可以实例化出多个对象。

举个简单的例子,假设我们有一个类 Car,用来表示汽车,其中包含了品牌、型号和颜色等属性,以及启动、加速和刹车等方法。我们可以通过这个类来实例化多个不同的汽车对象。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring> // 添加头文件来使用 strcpy 函数

using namespace std;

class Car {
public:
    char brand[20]; // 使用字符数组来表示品牌
    char model[20]; // 使用字符数组来表示型号
    char color[20]; // 使用字符数组来表示颜色
};

int main() {
    // 创建两个 Car 对象
    Car car1;
    strcpy(car1.brand, "Toyota"); // 使用 strcpy 函数将字符串复制到字符数组中
    strcpy(car1.model, "Camry");
    strcpy(car1.color, "blue");

    Car car2;
    strcpy(car2.brand, "Honda");
    strcpy(car2.model, "Accord");
    strcpy(car2.color, "red");


    return 0;
}

补充类的大小和实例化对象的大小:

在 C++ 中,当你定义一个类时,这个类本身并不会在内存中分配空间,因为类只是一个模板或者蓝图,用来描述对象的结构和行为。只有当你实例化(即创建)一个类的对象时,这个对象才会在内存中分配空间。 但是你可以计算南图的大小来推出它实际出来的对象的大小,就比如有一张房子的平面图,在有标注的情况下,你可以计算它实际的按照图纸建造的大小,实际装修出来的款式又不太相同(实例化出不同的对象):

在这里插入图片描述

空对象有没有大小(即空的南图):

在这里插入图片描述

空对象也有大小,这是因为C++标准要求每个对象在内存中必须有一个非零的大小,即使这个对象没有任何成员。这一规定是为了确保每个对象在内存中都有独一无二的地址,以便于在程序中进行地址运算和指针操作。也可以认为,房子平面设计图什么都没有,但还是有个图表示它是房子的平面设计图。

如何计算类的大小和它实例化的大小:

前面我们举例车类:我们来计算车类及实例化对象的大小

是否计算成员函数:
#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <cstring> // 添加头文件来使用 strcpy 函数

using namespace std;

class Car {
public:
    char brand[20]; // 使用字符数组来表示品牌
    char model[20]; // 使用字符数组来表示型号
    char color[20]; // 使用字符数组来表示颜色

    void start() {
        cout << "The " << color << " " << brand << " " << model << " starts." << endl;
    }

    void accelerate() {
        cout << "The " << color << " " << brand << " " << model << " accelerates." << endl;
    }

    void brake() {
        cout << "The " << color << " " << brand << " " << model << " brakes." << endl;
    }
};

int main() {
    // 创建两个 Car 对象
    Car car1;
    strcpy(car1.brand, "Toyota"); // 使用 strcpy 函数将字符串复制到字符数组中
    strcpy(car1.model, "Camry");
    strcpy(car1.color, "blue");

    Car car2;
    strcpy(car2.brand, "Honda");
    strcpy(car2.model, "Accord");
    strcpy(car2.color, "red");

    
    cout << sizeof(car1) << endl;
    cout << sizeof(Car) << endl;
    return 0;
}

在这里插入图片描述
在这里插入图片描述

此时我们发现屏蔽了成员函数,它的大小并没有改变:这是因为当计算一个类实例化对象的大小时sizeof 运算符只考虑对象中存储的数据,而不包括成员函数。成员函数只是描述了类的行为,但不会在对象中存储任何数据。因此,即使类中有成员函数,但它们不会增加对象的大小,因为对象只需要足够的空间来存储成员变量的数据,而不需要额外的空间来存储成员函数。通俗的来讲:当我们创建一个对象时,它只包含了类中定义的成员变量的数据,而不包括成员函数。就像一个人的身高和体重是人的实际数据,而他们的行为(例如跑步或吃饭)并不占据额外的空间一样。因此,在计算对象的大小时,不会考虑成员函数。

为什么实例化对象不计算成员函数的大小:

如果一个类实例化成多个对象,要计算成员函数的大小:

在这里插入图片描述

每个对象要计算成员函数非常的浪费空间,编译器的做法是把成员函数放在公共区域:在C++中,类的成员函数(无论是否实例化)都存储在代码区

实例化的类怎样访问成员函数:

成员函数放在公共区,实例化的类该怎么访问呢,成员函数是类的一部分,它们被视为类的内部所以可以通过成员访问操作符"."来访问

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring> // 添加头文件来使用 strcpy 函数

using namespace std;

class Car {
public:
    char brand[20]; // 使用字符数组来表示品牌
    char model[20]; // 使用字符数组来表示型号
    char color[20]; // 使用字符数组来表示颜色

   void start() {
       cout << "The " << color << " " << brand << " " << model << " brakes." << endl;
    }

    void accelerate() {
        cout << "The " << color << " " << brand << " " << model << " brakes." << endl;
    }

    void brake() {
        cout << "The " << color << " " << brand << " " << model << " brakes." << endl;
    }
};

int main() {
    // 创建两个 Car 对象
    Car car1;
    strcpy(car1.brand, "Toyota"); // 使用 strcpy 函数将字符串复制到字符数组中
    strcpy(car1.model, "Camry");
    strcpy(car1.color, "blue");

    Car car2;
    strcpy(car2.brand, "Honda");
    strcpy(car2.model, "Accord");
    strcpy(car2.color, "red");

    // 调用对象的方法
    car1.start();
    car2.start();
  

    return 0;
}

在这里插入图片描述

在这里插入图片描述

此时看到两个不同的对象都是call 的Car::accelerate(05212D0h)相同的函数:但是他们的输出结果不同。

this指针的出现:

为了保证输出的结果不同,C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

在这里插入图片描述
在这里插入图片描述

不可以显示传递

在这里插入图片描述

在这里插入图片描述

this指针的特性:
  1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值 。 在成员函数内部,this 指针是一个指向当前对象的指针,但在 C++ 的语法中并没有直接声明 this 指针的类型的语法。编译器会隐式地将 this 指针视为指向当前类类型的指针,并且在非常量成员函数中,this 指针的类型是 ClassName* const,其中 ClassName 是当前类的名称。

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述

  1. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
  2. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。

我们看这个例子,是把car1,car2的this指针放在ecx寄存器

在这里插入图片描述

类的大小对实例化的作用:

sizeof运算符可以用来计算一个类型或一个表达式的字节大小。它的结果是在编译时就确定的,不需要等到运行时才能知道。所以,即使你没有实例化一个类的对象,你也可以用sizeof来计算这个类的大小,因为这个大小只和类的定义有关,而不和类的对象有关。通俗的来说,你有了房子平面设计图及相关参数就可以算出房子实际的大小。但是只是算出了大小,并没有真正建造,实例化对象才算是建造。

对于类的大小,主要是指在程序中定义了一个类之后,这个类所占用的内存空间大小。类本身在编译时并不会直接分配内存空间,但编译器需要知道这个类所占用的内存空间大小,以便在创建对象时进行内存分配。

编译器的内存对齐规则:

只计算成员变量跟c语言的内存对齐规则一致
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

实例:

struct s1
{
	char c1;
	int i;
	char c2;
};
int main() {
	printf("%d\n", sizeof(struct s1));
}

我们来画图分析:第一个成员在与结构体变量偏移量为0的地址处

​​​​在这里插入图片描述

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

开始放第二成员,int类型4个字节 VS中默认的值为8,4<8对齐数4

在这里插入图片描述

开始放第三成员,char类型一个字节 VS中默认的值为8,1<8对齐数1,此时8为1的对奇数,8存放char c

​​​​​​在这里插入图片描述

结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。前面我们看到最大对齐数位4,此处为9,不是整数倍,所以要继续往后面

在这里插入图片描述
在这里插入图片描述

为了满足对齐规则,上面有6个字节浪费;

为什么存在内存对齐:
  1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。
  2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

在这里插入图片描述

内存对齐是拿空间来换取时间的做法那在设计类的时候,我们既要满足对齐,又要节省空间,如何做到让占用空间小的成员尽量集中在一起

补充成员函数声明和定义:

声明和定义全部放在类体中,需注意:成员函数如果在类中定义,函数默认是内联函数

内联函数的补充:

内联函数的补充:

内联函数只是个建议具体是编译器自己决定,对于函数体较小 直接声明和定义放类一起当内联函数使用。
对于函数体大的,声明和定义全部放在类体中,此时的内联函数的声明就不起作用,当非内联函数处理。

验证:函数体较小在类里当内联函数使用,函数体大的在函数体就不是内联函数了
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring> // 添加头文件来使用 strcpy 函数

using namespace std;

class Car {
public:
    char brand[20]; // 使用字符数组来表示品牌
    char model[20]; // 使用字符数组来表示型号
    char color[20]; // 使用字符数组来表示颜色

    inline void start() {
        cout << "1111" << endl;
    }

    void accelerate() {
        cout<<"1111"<<endl;
    }

    void brake() {
        cout << "The " << color << " " << brand << " " << model << " brakes." << endl;
    }
};

int main() {
    // 创建两个 Car 对象
    Car car1;
    strcpy(car1.brand, "Toyota"); // 使用 strcpy 函数将字符串复制到字符数组中
    strcpy(car1.model, "Camry");
    strcpy(car1.color, "blue");

    Car car2;
    strcpy(car2.brand, "Honda");
    strcpy(car2.model, "Accord");
    strcpy(car2.color, "red");

    // 调用对象的方法
    car1.start();
    car1.accelerate();
    car1.brake();

    car2.start();
    car2.accelerate();
    car2.brake();

    return 0;
}

在这里插入图片描述
在这里插入图片描述

对于前两个函数,函数体不大,直接就在main函数栈帧展开,对于brake函数这种大的函数体(主要是<<操作多)就call ,并没有展开。

这是brake函数的反汇编:

在这里插入图片描述

声明定义什么场景适合分离:

通过上面的学习,我们可以知晓了成员函数如果在类中定义,函数默认是内联函数 ,就衍生出两种:1.在类里面声明定义一起2.在类里面声明,类外面定义。

1.小函数体在类里面声明定义一起:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

此时我们看到是直接展开的;

2.函数体无论大小,声明定义分离:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们来观察,看到是直接call的,如果想把它变成内联函数,内联函数的声明定义不能分离(即放在同一文件)。

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值