news 2026/9/27 9:39:15

从结构体到对象:正式学习C++类的骨架、封装与this指针

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从结构体到对象:正式学习C++类的骨架、封装与this指针

文章目录

    • 1. C++在C后的延伸
    • 2. 类的基本骨架:成员、类域和定义位置
      • 2. 类定义中放什么
      • 2.2 类内定义与类外定义
      • 2.3 class 和 struct 的差别
    • 3. 访问限定符:封装不是仅仅把数据藏起来
      • 3.1 public 和 private
      • 3.2 protected 先知晓边界即可
    • 4. 对象大小:成员变量属于对象,成员函数不按对象重复保存
    • 5. this 指针:成员函数如何找到当前对象
      • 5.1 从两个对象调用同一个函数
      • 5.2 `this` 的几个性质
      • 5.3 空指针调用成员函数为什么有时不马上崩溃
    • 7. 小结

1. C++在C后的延伸


C++在保留了大量C的表达方式的同时,增加了更强的类型组织和抽象工具,一些数据与操作数据的函数可以被组织到同一个类中,这正是C++面向对象的开始。

在C中,我们可能这样表示一个日期:

typedefstructDate{intyear;intmonth;intday;}Date;voidDateInit(Date*d,intyear,intmonth,intday){d->year=year;d->month=month;d->day=day;}voidDatePrint(constDate*d){printf("%d/%d/%d\n",d->year,d->month,d->day);}

调用时必须把对象地址显式传给每一个函数:

Date d;DateInit(&d,2026,9,24);DatePrint(&d);

这种写法可以工作,但是它把“哪些函数属于Date”交给了命名习惯来维护。函数写错参数、直接修改不应该暴露的数据与忘记检查日期等等操作是否合乎规则,都只能依靠调用者自觉,安全性较低。

而C++类则把这层关系写进类型本身:

classDate{public:voidInit(intyear,intmonth,intday);voidPrint()const;private:int_year=0;int_month=0;int_day=0;};

调用时变成:

Date d;d.Init(2026,9,24);d.Print();

2. 类的基本骨架:成员、类域和定义位置


2. 类定义中放什么

classCounter{public:voidAdd(intvalue){_value+=value;}intGet()const{return_value;}private:int_value=0;};

这里定义的是一个计数器对象,其拥有其的私有成员变量_value,然后拥有两个成员函数,一个用于获取当前计数,一个用于增加指定数量。

class Counter { ... };中的内容叫类体。类体里的变量叫成员变量,函数叫成员函数。类定义最后的分号不能省略,因为整个类定义在语法上也是一条声明。

下面我们创建两个对象first和second,让其增加不同的计数:

Counter first;Counter second;first.Add(3);second.Add(10);std::cout<<"first当前计数:"<<first.Get()<<std::endl;std::cout<<"second当前计数:"<<second.Get()<<std::endl;

运行示例:

可以看到,first和second是两个对象,各自拥有一份_value。

2.2 类内定义与类外定义

代码量较少的成员函数可以直接写在类内;较长的函数则通常在类外定义,让类体更像一份接口说明:

classCounter{public:voidAdd(intvalue);intGet()const;private:int_value=0;};voidCounter::Add(intvalue){_value+=value;}intCounter::Get()const{return_value;}

Counter::Add中的::表示“到Counter这个类域中寻找Add”。如果省略Counter::,编译器会把它当成普通的全局函数,无法直接访问_value。

对于成员函数的函数头后加的const表示的是此函数不会修改对象的相关成员变量,是保护数据的一种方式,如果函数不需要修改成员变量的话建议加上,增加代码可靠性。

2.3 class 和 struct 的差别

C++ 中的 struct 也可以包含成员函数和访问限定符:

structPoint{voidPrint()const;int_x=0;int_y=0;};

但两者的主要默认规则不同:class 的默认访问权限是 private,struct 的默认访问权限是 public。

C++ 中可以继续使用struct表示简单数据,也可以使用class表示需要封装行为的类型。关键不在于关键字哪个“更高级”,而在于你是否清楚哪些成员应该暴露给外部

  • struct:适合纯数据载体(类似 C 结构体),数据默认公开,简单方便;
  • class:适合需要封装的类型,内部数据默认私有,控制对外接口。

3. 访问限定符:封装不是仅仅把数据藏起来


3.1 public 和 private

classCounter{public:voidAdd(intvalue){if(value>0){_value+=value;}}intGet()const{return_value;}private:int_value=0;};

当外部想要使用成员变量_value时,只能通过 Add 和 Get 使用:

SafeCounter counter;counter.Add(5);counter._value=-100;// 编译错误:_value 是 private

封装的价值是把规则集中在类内部。以后如果计数器需要限制最大值,只改Add就可以;调用者不需要知道_value的具体表示方式。

访问限定符的作用范围从它出现的位置开始,一直持续到下一个访问限定符或类定义结束(默认是private):

classExample{int_privateValue=0;// 默认 privatepublic:voidSet(intvalue){_privateValue=value;}private:int_anotherValue=0;};

3.2 protected 先知晓边界即可

protected 在类外不能直接访问,和 private 在当前阶段的表现相同。它主要会在继承中体现差异。开始学习类和对象时,只需要重点明白:需要对外提供的操作放到 public,表示细节的数据和辅助函数通常放到 private。

4. 对象大小:成员变量属于对象,成员函数不按对象重复保存


观察下面的类型:

classSmall{public:voidPrint()const{}private:int_value=0;};classEmpty{public:voidPrint()const{}};

第一个Small类中有一个成员变量,而Empty中无成员变量,让我们看看其各自的对象大小:

std::cout<<"Small类大小:"<<sizeof(Small)<<std::endl;std::cout<<"Empty类大小:"<<sizeof(Empty)<<std::endl;

运行示例:

可以看到,Small至少需要容纳一个int(4个字节)。Empty没有成员变量,通常仍然占 1 个字节,用来区分不同对象的地址。

成员函数编译后是指令,不需要在每个对象里复制一份,因此一般不计入对象大小。

这就引出了成员变量与成员函数的不同存放方式:

成员变量 vs 成员函数

  • 成员变量:每个对象独立一份,其对应内部的成员变量有各自的内存空间。
  • 成员函数:整个程序里只有一份,所有对象共用。

成员函数的代码通常保存一份,调用时会根据对象不同而操作对应的成员变量。
其内部会依靠编译器隐式传入的this指针实现。

类的大小实际和C中结构体相似,还是会受到内存对齐影响,规则类似:

  1. 第一个成员通常从对象起始地址开始;
  2. 后续成员可能为了满足对齐要求产生填充;
  3. sizeof是编译器根据布局规则计算出的结果,不要只用成员大小简单相加。

5. this 指针:成员函数如何找到当前对象


5.1 从两个对象调用同一个函数

classDate{public:voidInit(intyear,intmonth,intday){_year=year;_month=month;_day=day;}voidPrint()const{std::cout<<_year<<'/'<<_month<<'/'<<_day<<std::endl;}private:int_year=0;int_month=0;int_day=0;};intmain(){Date d1;Date d2;d1.Init(2026,9,24);d2.Init(2027,1,1);d1.Print();d2.Print();}

Init的函数体没有写d1或d2,但两次调用结果不同。原因就是是非静态成员函数会有一个编译器隐含传入的参数:this。

可以把调用过程近似理解为:

d1.Init(2026, 9, 24) -> 类似 Init(&d1, 2026, 9, 24) d2.Init(2027, 1, 1); -> 类似 Init(&d2, 2027, 1, 1)

在真实的 C++ 语法中,我们不需要在调用时手写这个参数,但成员函数内部确实可以使用this:

voidDate::Init(intyear,intmonth,intday){this->_year=year;this->_month=month;this->_day=day;}

在编写时通常可以省略this->,因为编译器知道_year是当前对象的成员。显式写出this指针常用于成员变量和参数同名时,或者是为了强调当前对象。

5.2this的几个性质

  • this 是成员函数内部可用的指针;
  • 它指向调用当前成员函数的对象;
  • 不能修改 this 让它指向另一个对象;
  • 静态成员函数没有 this,因为它不依附于某个具体对象。

5.3 空指针调用成员函数为什么有时不马上崩溃

观察并运行以下代码:

classDemo{public:voidHello()const{std::cout<<"hello"<<std::endl;}voidPrintValue()const{std::cout<<_value<<std::endl;}private:int_value=10;};intmain(){Demo*ptr=nullptr;ptr->Hello();// 可能看起来能运行ptr->PrintValue();// 访问成员数据,通常会崩溃}

运行示例:


从语言规则上说,通过空指针调用非静态成员函数本身就是未定义行为,不能因为某次运行没有崩溃就认为它是安全的。

PrintValue需要通过this找到_value,所以更容易触发崩溃。

注意不要把这种使用方式当作技巧,而是通过这个例子帮助我们理解this指针的存在与作用。

7. 小结


类不是把 C 语法换一种写法,而是把数据、操作和访问边界组织到一个类型中。对象拥有自己的状态,成员函数通过隐含的 this 找到当前对象,访问限定符则决定外部能否直接触碰这些状态。理解这三层关系后,构造函数、析构函数和运算符重载就不再是孤立语法,而是继续完善“对象类型”的工具。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 9:32:23

IDEA搭建SpringMVC入门程序(1)

工具&#xff1a; IDEA 2020.1 Ultimate版 maven 3.6.3 JDK 1.8 1.创建项目 打开IDEA&#xff0c;点击“File—>New—>Project” 我们 选择“Maven”项目&#xff0c; JDK这里选择1.8版本&#xff0c;下面勾选maven-archtype-webapp,然后点击“Next” 来到下面的界面…

作者头像 李华
网站建设 2026/9/27 9:31:27

第245篇_出入境政策与口岸通关信息采集

【Python爬虫实战】第245篇:政务信息怎么抓——出入境政策与口岸通关信息采集实战 所属专栏:【Python爬虫实战】从零到企业级爬虫工程师(CSDN 付费专栏) 本篇篇目:第 245 篇(政务与公共信息采集专场,每篇都是一个可独立上手的实战项目) 难度等级:中级,需要掌握 reque…

作者头像 李华
网站建设 2026/9/27 9:30:56

使用docker-compose部署项目(mysql、springboot、vue、nginx)

结合前面几篇文章&#xff1a; 9.如何将jar包打包成docker镜像并进行部署 10.docker如何部署一个前端网站 docker系列文章&#xff1a; https://blog.csdn.net/baidu_32523857/article/details/135212681 这篇文章我们使用docker-compose来部署项目。 docker-compose是一个编…

作者头像 李华
网站建设 2026/9/27 9:23:04

动态住宅IP免费用!免费用! 全球200+国家,4700W+动态IP !

【全球代理IP免费测试】 &#x1f4cd; 24 小时单 IP 最高使用时长 &#xff1a; 支持 24 小时灵活切换 IP 地址&#xff0c;适应不同网络场景&#xff0c;满足多样化业务需求&#x1f6e1;️ 4700 万全球 IP 池 : 遍布 190 个国家的真实家庭住宅代理&#xff0c;合规获取&…

作者头像 李华