C++主题年技巧积累#2——我被static撞了一下腰

C++主题年技巧积累#2——我被static撞了一下腰
优快云 旗下水之真谛 http://blog.youkuaiyun.com/FantasiaX )出品
 
前传:
         刚刚参加博文视点出版社三周年庆典回来,兴奋之余想到今天还没有更新Blog,于是跑上来更新一下——我尽量“好好学习,天天上博”。哎呀,今天见到好多名人啊!先是在金戈老师旁边坐下,然后又去问候了久仰大名的孟岩老师,在孟老师的帮助下又找到《Beginning C# Object》的译者,也是本次晚会的摄像师——韩磊老师,你问韩磊唱歌没?没有!歌被欧阳璟(《程序员》的老编,大帅哥!还是本次年会的摄影师)给唱了。晚会的精彩那没的说,大家等着看视频吧,估计优快云上不久就会放出来。会间在杨福川的带领下,先是见到了梁晶编辑,然后又见到了亲自为我斧正译文的方舟老师。方舟老师人真不错,几分钟里还抓紧时间教我应该怎么译技术文章,怎么理顺段落、句型,如何重组定语、去掉不必要的连词……在这里,我深深地向您鞠上一躬,道一声:谢谢!同时也感谢梁编辑的不懈努力!接下来,我有幸见到了博文视点的周筠老师和优快云的总裁蒋涛先生。过程见还见到了很多平时“只见MSN不见人”的朋友,其中包括帅哥佘广。最后还有机会见到技术专家金旭亮老师。
         庆典参加后,感觉博文视点出版社和优快云(包括《程序员》杂志)真是两个充满激情、活力四射的团队!衷心地祝福博文视点出版社出版更多优秀的技术书籍——就像席间韩磊老师说的:出一本是一本,不糟踏书,人家买书,是真想学东西啊!也祝优快云和《程序员》杂志越办越红火!
打油诗一首,赠予辛勤工作在出版一线的朋友们,祝你们的2007年万事如意!诗名《日出》,意思是希望博文视点出版社能“天天出书,天天出好书”——夫,日出,日日出,又日出!
日出
    学海茫茫寻师苦,
    博文乐把众人渡。
    书香满载齐挥桨,
    破浪乘风向日出!
 
小序:
         咳咳,收心啦收心啦!热闹是人家的,知识是自己的。还是收回心来写自己的技术心得吧。话说“由俭入奢易,由奢入俭难”啊……这话一点不假,连写程序都是这样。像我,以前用惯了语法舒服流畅的C#,再回过头来学C++就深刻地体验到了这一点:C#之所以用起来简洁舒服,是因为它把很多应该由程序员自己做的事情(比如分配和释放内存)都替程序员做了。换句话说,C#和C/C++的设计理念是不一样的,C/C++假设程序做的一切事情(包括某些事情不去做)都是正确的,相信程序员是聪明人,决不为程序员多做任何事情;C#正好反过来,认为程序员应该不去关心底层这些东西、由程序员关注底层是容易出错的、程序员总会忘记在该分配内存的地方分配内存或者在该释放内存的地方释放内存(微软是怀疑程序员都是傻瓜还是打算把程序员都培养成傻瓜?),总之,程序员应该更多地去关心架构和实现,而不是这些细枝末节的东西……
         于是,我被培养成了一个不折不扣的“傻瓜型”程序员。所以,今天也就理所当然地被static关键字撞了一下腰、绊了一个跤。
 
正文:
         一切都源于我在写练习程序时那一瞬间的妄想……
         static关键字?小菜,C#和C++里都有,原理是一样的,会了C#还写不出C++的来?看着!咱这就一样写一个出来!
         先来C#的!
//----------------水之真谛 ----------------//
// http://blog.youkuaiyun.com/FantasiaX
//-----------------------------------------------//

using
System;
class
Student
{
    public static void Report() // C#
中成员方法的声明和定义合二为一
    {
        Console.WriteLine("I am a C# student.");
    }
}

class
Program
{
    static void Main(string[] args)
    {
        Student.Report();
// OK
    }
}

         再来C++的!
#include <iostream>

class Student
{
public:
         static void Report()    // C++
成员函数的声明和定义亦可合二为一,但决不是上策!
         {
                   std::cout<<"I am a C++ student."<<std::endl;
         }
};

int main(int argc, char *argv[])
{
         Student::Report();
// OK
         return 0;
}


         哈哈!一次编译通过!我说吗,会C#就会C++,没问题的,只是语法结构上稍有不同。
         接下来再来两个静态成员变量的例子!还是先写C#的
//----------------水之真谛 ----------------//
// http://blog.youkuaiyun.com/FantasiaX
//-----------------------------------------------//

using
System;
class
Student
{
    public static string Name; //
猜猜在这里C#都做了些什么?
}

class
Program
{
    static void Main(string[] args)
    {
        Student.Name = "Tim"
// OK
        Console.WriteLine(Student.Name);
    }
}

         乘胜追击,再来C++的!
#include <iostream>
#include
<string>

class Student
{
public:
         static std::string Name; //
猜猜C++在这里做什么了?
};

int main(int argc, char *argv[])
{
         Student::Name = "Tim"
//Error!!!   狂汗~~~ >_<
         std::cout<<Student::Name<<std::endl;
         return 0;
}
         啊~~~竟然有错误!这是怎么回事呢?不应该啊~~~C#里明明能行的~~~
         百思不得其解后,拿出《C++ Primer 第四版(中文)》来查阅……果然找到很多有用信息。
l         P.399 Row4-5.         ……static数据成员独立于该类的任意对象而存在……
l         P.399 Row5-6.         ……每个static数据成员是与类关联的对象,并不与该类的对象关联……
l         P.401 Row1.             ……static数据成员必须在类定义体的外部定义(正好一次)。
注解:数据成员(Data Member)其实就是成员变量。
         上面最有用的信息就是第三条了:原来还需要在类的外部定义一次!于是修改代码为:
#include <iostream>
#include
<string>

class Student
{
public:
         static std::string Name; //
猜猜C++在这里做什么了?
};

std::string Student::Name = std::string(); // 类外定义,这句是新添加的。


int main(int argc, char *argv[])
{
         Student::Name = "Tim"
// OK
         std::cout<<Student::Name<<std::endl;
         return 0;
}


         问题虽然解决了,但决不能轻易放过这个问题——为什么会这样呢?
 
深挖:
         我把目光移到在《C++ Primer》里找到的前两条消息上。第二条与C#一致,说明不了什么问题,第一条倒是让人眼前一亮——static数据成员独立于该类的任意对象而存在。果真是这样吗?我得自己动手验证一下:
         先写一个类:
class Student
{
public:
         int Age;
};

         然后执行std::cout<<sizeof(Student)<<std::endl; 得到的结果是4。
         把类改写成这样:
class Student
{
public:
         int Age;
         unsigned int StudentID;
};
         结果是8——预料之中。
         再改写成:
class Student
{
public:
         int Age;
         unsigned int StudentID;
         double Score;
};

         结果是16——可以理解。
         然后加一个static成员试试——
class Student
{
public:
         int Age;
         unsigned int StudentID;
         double Score;
         static int Amount;
};

         结果还是16,没有变!原来static的成员数据真的是独立于类空间之外的
         问题解决了吗?No!不但没有解决,反而增多了,一个问题变成了两个问题——
1.         类所占的空间本质是什么?
2.         类的static数据成员究竟在哪里?
第一个问题似乎比较好回答:说“类所占的空间”其实欠妥,应该是“类的实例占内存的大小”。
拿上面这个类来说,它的每一个实例中都将包含三个子数据成员(int Age, double Score, double Score),所以会占去16个字节。而这16个字节将会由new操作符在内存中分配出来,并可以在Student类的构造函数去初始化它们——我们没有显式地初始化它们,所以它们的值是一个由上帝掷骰子得出来的值(使用VC执行的时候如果弹出错误警告,请按Ignore,我得到的Age值是-858993460,阴寿乎?)。
问题回答完了吗?没有!
提问:分配了16个字节的内存说明了什么问题?
回答:说明每个非static成员变量都有自己的内存,叠加起来占16字节。
提问:每个非static成员变量都有自己的内存说明了什么问题?
回答:说明为每个变量都分配了内存(哪儿来的砖??!!)
提问:为变量分配了内存说明了什么?
回答:说明每个非static变量不但已经被声明,而且已经被定义了!因为声明变量并不分配内存,只有定义变量的时候才分配内存!
         这才是第一个问题的本质的答案!
 
         第二个问题在《C++ Primer》这本书里就找不到答案了——毕竟是Primer,不是Advanced。于是又祭出宝卷《深入探索C++对象模型》。一番查阅后,在这里找到半个答案:
l         P. 95 Static Data Members节        Static data members,按其字面意义,被编译器提出于class之外……并被视为一个global变量……每一个static data member只有一个实体,存放在程序的data segment之中……
My God! 多么精彩的描述!!第二个问题的答案就是:类的static数据成员会像一个全局(global)变量一样被放在程序的数据段里(谁说大学开的汇编语言课没用来着??),而不占类实例的内存。
         基于此,我们可以推敲出至少两点:
1.         类的static数据成员不占类(的实例)的内存,因此这里只是个声明、没有定义。
2.         “被视为”global变量仅仅是“被视为”,并不像声明并定义了一个真正的global变量。
所以,无论怎么说,我们都欠它一次“定义”——问题彻底解决了!
 
多做之过?What’s under the C#’s hood?
         也许你会问:为了一个小小的static,至于吗?我会坚定地告诉你——至于!不仅仅是因为它让我闪了腰,更因为我们作为程序员,要有严谨的态度和钻研的精神!
         最后收拾一把C#——为什么它的static成员变量就可以直接使用?
         其实上面已经说过,C#的设计理念是把程序员从时时刻刻记着与内存打交道的繁枝缛节中解放出来,因此C#类中的一句public static string Name;不仅仅是一个声明,顺便连定义也做了
         C#替程序员做的还远不止一个变量的定义。当你试图输出一个没有显式初始化的C#类数据成员(无论是static的还是非static的),都会得到一个默认的“零”值。在MSDN里有张表格——Default Values Table (C# Reference),大家可以参阅。简摘如下:
Value type
Default value
false
0
'/0'
0.0M
0.0D
The value produced by the expression (E)0, where E is the enum identifier.
0.0F
int
0
0L
0
0
The value produced by setting all value-type fields to their default values and all reference-type fields to null.
0
0
0
         那么这个“零”值是怎么得到的呢?这又得感谢C#的“多做”了——对于C#类的非static数据成员,C#编译器会在自动添加的实例构造函数中给它们赋上“零”值;而对于C#类的static数据成员,C#编译器会在自动添加的类构造函数中为它们赋“零”值。(注意:最后这一段描述来源于我的记忆,具体出处已无从考证,也许来自某帖子?也许是刘德华上次遭枪杀时那张报纸里的内容?唉……是自己梦中杜撰出来的也说不定,所以请大家多加小心咯
 
         夜已经很深了……睡觉了。今天的入眠曲是When You KnowThe Shadow of Your Smile
         她……还好吗?
 
 
法律声明:本文章受到知识产权法保护,任何单位或个人若需要转载此文,必需保证文章的完整性(未经作者许可的任何删节或改动将视为侵权行为)。若您需要转载,请务必注明文章出处为优快云以保障网站的权益;请务必注明文章作者为刘铁猛http://blog.youkuaiyun.com/FantasiaX),并向liutm@beyondsoft.com发送邮件,标明文章位置及用途。转载时请将此法律声明一并转载,谢谢!


Trackback: http://tb.blog.youkuaiyun.com/TrackBack.aspx?PostId=1485195
 
### C++ 插件开发概述 C++ 插件开发是一种常见的技术手段,特别是在游戏开发领域。它允许开发者通过外部代码扩展现有系统的功能,从而满足特定需求。以下是关于 C++ 插件开发的一些核心概念和技术要点。 --- #### 动态链接库 (DLL/So) 的创建与使用 为了在其他环境中集成 C++ 代码,通常会将其编译为动态链接库文件(Windows 上为 `.dll`,Linux 和 macOS 上为 `.so` 或 `.dylib`)。这些库可以通过宿主环境加载并调用其中的功能。例如,在 Unity 中,可以按照以下方式操作: 1. **编写 C++ 函数** 使用 `extern "C"` 声明导出函数以防止名称修饰问题,并确保函数可以在其他语言中被正确调用。 ```cpp // MyPlugin.cpp extern "C" { __declspec(dllexport) int AddNumbers(int a, int b) { return a + b; } } ``` 2. **编译为 DLL/SO 文件** 将上述代码编译为目标平台支持的动态链接库文件。具体方法取决于使用的构建工具链(如 Visual Studio、GCC 或 Clang)以及目标操作系统。 3. **在宿主程序中调用插件** 宿主程序(如 Unity 的 C# 脚本)可通过 P/Invoke 技术加载并调用该插件中的函数[^1]。 --- #### 接口设计的重要性 良好的插件设计应注重以下几个方面: - **解耦**: 确保插件逻辑独立于主应用程序的核心模块,便于维护和更新。 - **易用性**: 提供清晰简洁的 API,降低使用者的学习成本。 - **可扩展性**: 支持未来可能的需求变化或新功能引入[^3]。 实际开发中,建议采用面向对象编程的思想定义抽象类或接口作为桥梁,使插件能够灵活适配不同的场景。 --- #### 性能优化策略 由于许多应用场景涉及实时数据处理或高频率运算,因此性能成为不可忽视的因素之一。为此,需充分利用 C++ 特有的优势——高效内存管理和低级硬件访问能力[^2]。一些常见技巧如下: - 避免不必要的堆分配;尽可能重用已存在的缓冲区。 - 利用 SIMD 指令集加速数值密集型任务执行速度。 - 对频繁调用的方法实施内联化减少间接跳转开销。 --- #### UE5 C++ 插件开发实例分析 对于 Unreal Engine 5 用户而言,除了基本原理外还需掌握引擎特有的框架结构及其配套工具链特点。比如如何声明事件处理器、注册自定义组件类型等高级话题均会在实践中遇到挑战。下面给出一段简化版代码片段展示如何向蓝图暴露新的行为动作: ```cpp // Example.h UCLASS() class MYPLUGIN_API UExampleFunctionLibrary : public UBlueprintFunctionLibrary { GENERATED_BODY() public: UFUNCTION(BlueprintPure, Category="My Plugin") static float MultiplyByTwo(float Value); }; // Example.cpp float UExampleFunctionLibrary::MultiplyByTwo(float Value) { return Value * 2; } ``` 此例子展示了怎样借助宏定义快速搭建起可供蓝图为前端界面直接调取的基础服务单元. --- ### 结论 综上所述,C++ 插件开发不仅限于理论层面的知识学习还需要不断积累实践经验才能真正精通这项技能.无论是Unity还是Unreal这样的主流平台上都有各自独特的生态系统等待探索挖掘价值所在.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值