当前位置: 首页 > news >正文

【哇! C++】类和对象(一)

目录

一、类的引入和定义

1.1 类的引用

1.2 类的定义

二、类的访问限定符及封装

2.1 类的访问限定符

2.2 封装

三、类的作用域

四、对象实例化

五、类对象模型

5.1 类对象的储存方式

5.2 结构体内存对齐规则


一、类的引入和定义

1.1 类的引用

        C语言结构体只能定义变量,在C++中,结构体不仅可以定义变量,也可以定义函数。如下程序所示:

#include<iostream>
using namespace std;

struct Stack
{
    //成员变量
    int* a;
    int top;
    int capacity;

    //成员函数
    void Init(int n = 4)
    {
        a = (int*)malloc(sizeof(int) * n);
        if(a == nullptr)
        {
            perror("malloc");
            return;
        }
    }
    void Push(int x)
    {
        a[top++] = x;
    }
}

int main()
{
    struct Stack st1;//兼容C
    Stack st2;//C++用法
    st2.top = 0;//对象.成员
    st2.Init();
    st2.Push(1);
    st2.Push(2);
    st2.Push(3);
    st2.Push(4);

    return 0;
}

         C++兼容C,所以struct Stack st1;是没有问题的,C++的用法为Stack st2;

1.2 类的定义

        C++中把结构体升级为类,但同时又兼容C中的结构体,为进一步区分,C++引入关键字class替代struct定义类。其中:class为定义类的关键字,ClassName为类的名字,{ }中为类的主体。

class classname
{
    //类体:由成员变量和成员函数组成

    //成员变量
    //想在类中定义数据,一般情况,变量是private

    //成员函数
    //想在类中实现哪些方法,定义成员函数,一般情况,成员是public

}; //注意后边的分号

        C++推荐成员变量名字前加"_",这样可以很方便地区分成员变量和函数参数。例如,我们定义一个时间类:年-月-日,程序如下:

#include<iostream>
using namespace std;

class Date
{
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    int _year;
    int _month;
    int _day;
}

int main()
{
    Date d1;
    d1.Init(2024, 2, 17);

    return 0;
}

        程序运行后发现调不动,“Date::Init”: 无法访问 private 成员(在“Date”类中声明)。究其原因,在于没有加访问限定符,默认是private,所以把class换成struct,报错就消失了。想在类外直接访问,定义为public;反之定义为private:。

二、类的访问限定符及封装

2.1 类的访问限定符

        C++提出了3种访问限定符:public、protected、private。

  1. public修饰的成员在类外可以直接被访问;
  2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)。当前阶段可认为protected和private没有区别。

        没有加访问限定符的类,class默认的访问限定是private,struct默认的访问限定是public。程序展示如下:

#include<iostream>
using namespace std;

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()
{
    class Date d0;                //ok,但不这样用
    Date d1;                      //整体定义,对象实例化
    Date::_year;                  //通过类域不能访问,因为他只是声明,没有空间,就不能访问
    d1._year++;                   //当把_year前的private注释掉以后,ok
    cout << sizeof(d1) << endl;   //12

    d1.Init(2025, 2, 17);
    d1.Print();

    return 0;
}

        值得注意的是:在Date类private中的_year,_month,_day是定义还是声明? - 声明

        变量的声明和定义的区别:变量的定义是要开空间,即标志是开空间。

2.2 封装

面向对象的三大特性:封装、继承、多态。封装:本质是一种管控:

        1.C++数据和方法都放到类里面,C是分离的;

        2.C++强制加了访问限定符,去对成员进行限制,想给你访问的是public,不想给你访问的是private或protected。

三、类的作用域

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

//Stack.h
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;

class Stack
{
private:
	//成员变量
	int* a;
	int top;
	int capacity;
	
public:
	//成员函数
	void Init(int n = 4);
	void Push(int x);
};


//Stack.cpp
#include"Stack.h"

void Stack::Init(int n)
{
	a = (int*)malloc(sizeof(int) * n);
	if (nullptr == a)
	{
		perror("malloc");
		return;
	}
	capacity = n;
	top = 0;
}

void Stack::Push(int x)
{
	a[top++] = x;
}

        做类的声明和定义分离的时候需要注意:用之前C的方法定义,会报错,提示是未声明的标识符,即便改成public也不行。

        这是因为,编译器在搜索的时候,默认只在局部域和全局域搜索。在当前的局部没有a、capacity和top,全局也没有。a在Stack.h文件中的Stack这个类里面。在C++中,除了全局域,只要在花括号{ }中括起来的都叫一个域,所以,Stack类形成的新的域,类域,其概念有点像命名空间域。

        所以,语法规定,需要在函数名前加Stack::就让编译器认为Init和Push不是一个普通函数,而是Stack这个类中的成员函数,此时,a就先在局部域中找,再去类域中去找,最后再去全局域中找。

  • 局部域,就是一个函数,用花括号括起来的。局部域里边还可以再定义一个局部域,例如在for循环中再定义for循环,for循环中定义的内容无法在外部访问;
  • 全局域,没有被括起来,整个全局就是全局域;
  • 命名空间域,把特定的一些变量圈起来,如果没有指定或没有展开,就不会到命名空间域中搜索。

四、对象实例化

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

  1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;
  2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量;
  3. 做个比方,类实例化对象,有点像用图纸建造房子。类像图纸,类定义了有哪些数据,有哪些方法;房子图纸定义了房子是几室几厅几层。其中,成员函数,就是图纸中的健身房,电梯等公共的设施;成员变量,就是每家每户都需要的卧室、厨房等。

        所以也就解释了Data::_year++;是错误的。用类名直接指定成员访问,就好比,有一张图纸,去图纸中找卧室。所以,必须实例化才能指定访问。

五、类对象模型

5.1 类对象的储存方式

        对象里边存放数据时有两种方案:

        1. 对象里边既存成员变量,也存成员函数的地址。但这种方法会有缺陷:当有d1 d2 d3 d4...时,实例化后,成员变量可以单独存,但是成员函数的地址却都是一样的,存的多份相同的成员函数地址,造成浪费。

        2. 比较好的方式是:每个对象里边只存成员变量,在一个公共的代码区域存储成员函数的地址。如图所示:

        我们通过对下面的不同对象分别获取大小来分析看下:

#include<iostream>
using namespace std;

//类中仅有成员函数
class A1
{
public:
    void func()
    {
    
    }
};

//类中什么都没有---空类
class A2
{

}

//类中既有成员变量,又有成员函数
class A3
{
public:
    void f1()
    {

    }

private:
    int _a;
};

int main()
{

    cout << sizeof(A1) <<endl;    //1
    cout << sizeof(A2) <<endl;    //1
    cout << sizeof(A3) <<endl;    //4
    
    return 0;
}

        一个类的大小,实际就是该类中“成员变量”之和,当然要注意内存对齐没有成员变量的类对象,大小是1byte,占位,标识对象实例化时定义出来的存在过,所以不是0。

5.2 结构体内存对齐规则

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

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处;

        注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
        VS中默认的对齐数为8。

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

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

 

相关文章:

  • ⭐ Unity 横向滑动列表 首尾相连 轮转图
  • 在Linux上安装和使用Docker
  • 嵌入式linux利用标准字符驱动模型控制多个设备方法
  • STM32 USB 设备的描述信息作用
  • 【ISO 14229-1:2023 UDS诊断(ECU复位0x11服务)测试用例CAPL代码全解析⑰】
  • git,gitea - tool - creat Gitea Empty Rep Dir
  • 【异常错误】pycharm debug view变量的时候显示不全,中间会以...显示
  • nginx负载均衡, 解决iphash不均衡的问题之consistent
  • 【时时三省】(C语言基础)用N-S流程图表示算法
  • ok113i平台——多媒体播放器适配
  • “让App玩捉迷藏:Android教育平板的‘隐身术’开发实录”
  • 尚硅谷爬虫note009
  • 第一章:前端性能的定义与指标
  • DeepSeek联网搜索
  • Docker:3、在VSCode上安装并运行python程序或JavaScript程序
  • windows系统本地部署DeepSeek-R1全流程指南:Ollama+Docker+OpenWebUI
  • GitLab 概念
  • Python自动化测试
  • 【分布式理论12】事务协调者高可用:分布式选举算法
  • 详解Virtualhome环境搭建教程 | 智能体
  • 班级展示网站/南京seo优化推广
  • 内部网站开发/怎样建网站赚钱
  • 宁波住房和城乡建设培训网站/深圳网站制作推广
  • 甘肃网络公司网站建设/百度推广官网首页
  • 广州市酒店网站设计/seo搜索价格
  • 怎么做电子商务网站/seo管理平台