news 2026/5/16 0:59:18

成员函数与 this 指针:函数属于数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
成员函数与 this 指针:函数属于数据

文章目录

  • 引言
  • 一、从 C 到 C++:函数入住结构体
    • 1.1 C 的世界观:函数和数据是平行线
    • 1.2 C++ 的世界观:函数住在结构体里
  • 二、this 指针:编译器偷偷帮你的忙
    • 2.1 底层真相
    • 2.2 显式使用 this
    • 2.3 返回 *this:实现链式调用
  • 三、const 成员函数:this 变身 const
    • 3.1 const 对象的困境
    • 3.2 const 成员函数的作用
    • 3.3 mutable:const 函数中的"例外"
  • 四、static 成员函数:不拿 this 的函数
  • 五、编译器视角:成员函数到底长什么样
  • 六、完整对照与总结
  • 总结

本系列为《C++深度修炼:基础、STL源码与多线程实战》第4篇
前置条件:理解 C 语言函数指针和 struct,读过第2、3篇了解 class 与构造

引言

在 C 语言的世界观里,数据和函数是两个独立的东西。你定义结构体来存放数据,再定义函数来操作这些数据——它们之间唯一的联系,是函数名里的前缀和第一个参数的类型:

point_move(&p,x,y);rect_area(&r);bank_account_deposit(&acc,500);

C++ 翻转了这个关系:函数成为数据的一部分

p.move(x,y);r.area();acc.deposit(500);

这不是换了一种写法。这条.的背后,藏着一个 C++ 最核心的机制——this 指针。理解它在底层如何工作,你才能真正理解 const 成员函数、运算符重载、继承与多态。


一、从 C 到 C++:函数入住结构体

1.1 C 的世界观:函数和数据是平行线

// point_c.c#include<stdio.h>#include<math.h>structPoint{doublex,y;};voidpoint_init(structPoint*p,doublex,doubley){p->x=x;p->y=y;}voidpoint_move(structPoint*p,doubledx,doubledy){p->x+=dx;p->y+=dy;}doublepoint_distance(conststructPoint*a,conststructPoint*b){doubledx=a->x-b->x;doubledy=a->y-b->y;returnsqrt(dx*dx+dy*dy);}intmain(){structPointp1,p2;point_init(&p1,0,0);point_init(&p2,3,4);point_move(&p1,1,1);// 调用者必须知道 p1 传给哪个函数printf("distance = %.2f\n",point_distance(&p1,&p2));}

规则很清楚:每个函数接收一个指向struct Point的指针作为第一个参数,函数名以point_为前缀来表明归属。

1.2 C++ 的世界观:函数住在结构体里

// point_v1.cpp#include<cstdio>#include<cmath>classPoint{public:Point(doublex,doubley):x_(x),y_(y){}voidmove(doubledx,doubledy){x_+=dx;y_+=dy;}doubledistance(constPoint&other)const{doubledx=x_-other.x_;doubledy=y_-other.y_;returnsqrt(dx*dx+dy*dy);}private:doublex_,y_;};intmain(){Pointp1(0,0),p2(3,4);p1.move(1,1);// p1 是被操作的对象printf("distance = %.2f\n",p1.distance(p2));}

movedistance这两个函数住进了Point。调用时不再写point_move(&p1, 1, 1),而是写p1.move(1, 1)——“对象.操作”


二、this 指针:编译器偷偷帮你的忙

2.1 底层真相

当你写p1.move(1, 1)时,编译器实际上把它翻译成类似 C 的调用:

// 你写的:p1.move(1,1);// 编译器内心OS:Point::move(&p1,1,1);// ^^^^ 偷偷多传了一个指向 p1 的指针

这个隐藏的参数就是this——一个指向调用者对象的指针。在成员函数内部,你可以直接使用它。

2.2 显式使用 this

classPoint{public:Point(doublex,doubley){this->x_=x;// 等价于 x_ = x;this->y_=y;// 等价于 y_ = y;}voidmove(doubledx,doubledy){this->x_+=dx;// 等价于 x_ += dx;this->y_+=dy;// 等价于 y_ += dy;}// 参数名和成员名冲突时,this 区分歧义voidset_x(doublex_){// 参数叫 x_this->x_=x_;// this->x_ 是成员,x_ 是参数}private:doublex_,y_;};

💡 大多数时候this->可以省略,因为编译器会自动在成员变量前加this->。但有两种情况必须显式使用:

  • 参数名遮蔽了成员名(如上面的set_x
  • 需要返回对象自身时(如return *this;

2.3 返回 *this:实现链式调用

classAccumulator{public:Accumulator():total_(0){}Accumulator&add(intv){total_+=v;return*this;// 返回自身引用}Accumulator&multiply(intv){total_*=v;return*this;}intvalue()const{returntotal_;}private:inttotal_;};intmain(){Accumulator acc;acc.add(5).multiply(3).add(2);// 链式调用// ((5 + 0) * 3) + 2 = 17printf("%d\n",acc.value());// 17}

return *this返回了对象自身的引用,让下一个.可以继续链在同一个对象上。std::cout<<操作符就是靠这个模式工作的。


三、const 成员函数:this 变身 const

3.1 const 对象的困境

voidprint_point(constPoint&p){// p 是 const 引用——承诺不修改 p// 但编译器怎么知道 p.distance() 不会偷偷改 p 的成员?doubled=p.distance(other);// 能编译通过吗?}

答案取决于distance是否被标记为const成员函数

3.2 const 成员函数的作用

classPoint{public:// const 成员函数:承诺不修改成员变量doubledistance(constPoint&other)const{// this 的类型是 const Point*// 你不能在这里写 x_ += 1; —— 编译器报错doubledx=x_-other.x_;doubledy=y_-other.y_;returnsqrt(dx*dx+dy*dy);}// 非 const 成员函数:可能修改成员voidmove(doubledx,doubledy){x_+=dx;// 合法:可以修改}private:doublex_,y_;};voidprint_distance(constPoint&a,constPoint&b){// a.distance(b) 可以,因为 distance 是 const// a.move(1, 1) 不行!—— move 不是 const,可能修改对象}

规则很简单:

对象类型能调用非 const 函数能调用 const 函数
Point &
const Point &

const 是"承诺不修改"的签名——它让编译器和读者都能信任这个函数。

3.3 mutable:const 函数中的"例外"

有些时候,const 成员函数需要修改某些**不影响"逻辑状态"**的成员——比如缓存:

classExpensiveCalc{public:intcompute()const{if(!cached_){result_=heavy_computation();// const 方法中修改 result_?cached_=true;// 也修改 cached_?}returnresult_;}private:intheavy_computation()const{/* 耗时计算 */return42;}mutableboolcached_=false;// mutable:const 函数也能改mutableintresult_=0;// 同上};

mutable告诉编译器:“这个成员不影响对象的逻辑常量性,即使在 const 函数中也可以修改。”


四、static 成员函数:不拿 this 的函数

有些函数逻辑上属于这个类,但不需要操作具体对象

#include<cmath>classMathConstants{public:staticdoublepi(){return3.141592653589793;}staticdoublee(){return2.718281828459045;}};intmain(){// 用 类名::函数名() 调用,不需要对象doubleradius=5.0;doublecircumference=2*MathConstants::pi()*radius;}

static成员函数的核心特征:

特征普通成员函数static 成员函数
有 this 指针
能访问非 static 成员❌(没有 this,不知道是哪个对象的成员)
能访问 static 成员
调用方式obj.func()Class::func()obj.func()
可以是 const❌(没有 this,const 无从谈起)

典型用途:工厂方法、工具函数、命名空间式的函数分组。


五、编译器视角:成员函数到底长什么样

让我们回到最底层。C++ 的成员函数在编译后,本质上就是普通的 C 函数加了一个隐藏参数

classPoint{public:voidmove(doubledx,doubledy){x_+=dx;y_+=dy;}private:doublex_,y_;};

编译器会对move名称修饰(name mangling),把它变成类似这样的东西:

// 编译器生成的等价C代码(示意,实际mangling规则更复杂) void _ZN5Point4moveEdd(Point *this, double dx, double dy) { this->x_ += dx; this->y_ += dy; }

而你写的p.move(1.0, 2.0)被翻译成_ZN5Point4moveEdd(&p, 1.0, 2.0)

如果是 const 成员函数,this前面加const

doubledistance(constPoint&other)const;// → double _ZNK5Point8distanceERKS_(const Point *this, const Point &other);// ↑ 注意 const

🧠核心理解p.move(x, y)point_move(&p, x, y)在底层根本就是同一回事。C++ 没有增加任何运行时开销——它只是在语法层面重新组织了函数和数据的关系。


六、完整对照与总结

维度C 风格C++ 风格
函数定义void point_move(Point*, double, double)void move(double, double)在 class 内
调用语法point_move(&p, 1, 1)p.move(1, 1)
操作对象第一个参数惯例this指针隐式传递
const 保证const Point*是"建议"const 成员函数强制保证
静态函数普通 C 函数,靠前缀区分static成员函数,作用域在类内
链式调用返回指针point_move(&p)->...返回引用return *this

总结

C++ 成员函数和 this 指针的三个核心认知:

  1. 成员函数不是"凭空粘在对象上的魔法"——编译器只是把调用者对象的地址作为隐藏的第一个参数传进去,和 C 的struct *模式在底层是一回事
  2. const 成员函数是接口契约——它告诉调用者"我不会修改你",让 const 对象也能安全地使用这个函数。这是编译器级别的不变量保证
  3. static 成员函数是"属于类的工具函数"——没有 this,不能碰对象状态,适合工厂方法和常量定义

下一篇文章,我们将进入面向对象的第三个核心概念——继承。但我要先给你一个重要的提醒:继承不是"拿来用代码"的工具,而是一种严格的is-a关系。乱用继承比不用继承更危险。


📝动手练习

  1. 把第3篇的DynString类改写为支持链式调用的append方法(返回*this
  2. 写一个带mutable缓存的类,验证 const 成员函数确实可以修改mutable成员
  3. 用 GNU 的nm -C工具查看编译后的目标文件,观察成员函数的名称修饰(name mangling)结果
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/16 0:58:21

智能车竞赛技术全解析:从PID控制到嵌入式系统实战

1. 项目概述&#xff1a;一场关于速度与智慧的青春盛宴“飞思卡尔杯”智能车竞赛&#xff0c;这个名字对于很多电子、自动化、计算机相关专业的同学来说&#xff0c;绝对是一个如雷贯耳的存在。它不仅仅是一个比赛&#xff0c;更像是一个技术人的“成人礼”&#xff0c;一个将课…

作者头像 李华
网站建设 2026/5/16 0:57:50

龙虾热降温,我们到底需要什么样的 Agent?

责编 | 《AI 进化论》栏目组出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;过去几个月&#xff0c;AI Agent 无疑是技术圈最火热的词。我们聊颠覆、聊入口、聊取代……仿佛一夜之间&#xff0c;一个无所不能的“数字员工”就能接管我们的一切工作。热度之下&#…

作者头像 李华
网站建设 2026/5/16 0:57:15

AI智能发票核验管理系统:企业财税数字化核验新方案

在企业财税日常管理中&#xff0c;传统发票核验长期依赖人工录入、比对、审核&#xff0c;存在效率低、错审漏审、真伪难辨、重复报销等诸多问题。随着企业票据量激增&#xff0c;人工核验模式已无法适配现代化财税管理需求。AI智能发票核验管理系统依托OCR识别、多模态信息解析…

作者头像 李华
网站建设 2026/5/16 0:57:15

OpenClaw用户如何通过TaotokenCLI快速写入配置并开始使用

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 OpenClaw用户如何通过TaotokenCLI快速写入配置并开始使用 对于使用OpenClaw这类Agent工具的开发者来说&#xff0c;快速、正确地配…

作者头像 李华
网站建设 2026/5/16 0:52:30

基于CircuitPython与CLUE开发板的桌面自动浇花机器人DIY指南

1. 项目概述&#xff1a;一个会自己浇花的桌面机器人几年前&#xff0c;我养死了一盆心爱的琴叶榕&#xff0c;原因很简单&#xff1a;出差一周&#xff0c;忘了托人浇水。自那以后&#xff0c;我就一直在琢磨&#xff0c;能不能做个既有趣又实用的小玩意儿&#xff0c;让植物养…

作者头像 李华