【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

查看: 5783|回复: 10

游戏编程指南--导读

[复制链接]
发表于 2006-1-5 14:46:30 | 显示全部楼层 |阅读模式

作者:彭博 电脑来源:Internet

导 读

在开始阅读全文之前,希望你能抽出一些时间阅读这里的内容…

一、你想编一个怎样的游戏?

(1)星际争霸,帝国时代,英雄无敌,大富翁4,轩辕剑3,传奇,石器时代…

这些都是正宗的2D游戏,其标志是:视角完全固定或只有四个观察方向。这些游戏中特效不多,即使有也不需要使用汇编进行加速。

推荐阅读:第1、2、3、4、5章及第12章的相关部分。

可选阅读:第7、8章。如果需要网络功能,需阅读第11章。

(2)暗黑2,秦殇…

这是一类比较特殊的2D游戏,其特点在于各种特效(半透明,光影效果等)的大规模使用。有的此类游戏还可以使用3D加速卡来加速2D特效。

推荐阅读:第1、2、3、4、5、6章及第12章的相关部分。

可选阅读:第7、8、9、10章。如果需要网络功能,需阅读第11章。

由于现在的显卡几乎都能很好地支持3D加速功能,所以如果你打算放弃对没有3D加速卡的计算机的支持,可不阅读第4、5、6章,而推荐阅读第9章和第10章的第1、2节。

(3)反恐精英,雷神,魔兽争霸3,地牢围攻,FIFA,极品飞车,MU…

这些都是纯3D游戏,也代表了目前游戏的发展趋势。

推荐阅读:第1、2、3、7、9、10章及第12章的相关部分。

可选阅读:第8章。如果需要网络功能,需阅读第11章。

目 录
游戏编程指南 1
目 录 1
导 读 1
第一章 表述游戏的语言 1
1.1 VC.net概述 1
1.2 入门知识 4
1.2.1 数与数据类型 4
1.2.2 变量与常量 4
1.2.3 Namespace 5
1.2.4 操作符与表达式 6
1.3 预编译指令 7
1.4 结构,联合和枚举 8
1.4.1 结构 8
1.4.2 联合 9
1.4.3 枚举 10
1.5 控制语句 10
1.5.1 判断和跳转语句 10
1.5.2 选择语句 11
1.5.3 循环语句 13
1.6 函数 13
1.7 指针、数组与字符串 17
1.7.1 指针 17
1.7.2 数组 19
1.7.3 字符串 22
1.7.4 小结 23
1.8 多文件程序的结构 23
1.9 常用函数 25
第二章 如何说得更地道 29
2.1 定义和使用类 29
2.2 类的构造函数 32
2.3 类的静态成员 34
2.4 运算符重载 35
2.5 类的继承 38
2.6 虚函数和抽象类 41
2.7 模板 42
2.8 优化程序 45
2.9 调试程序 47
第三章 容纳游戏的空间 49
3.1 基本Windows程序 49
3.2 WinMain函数 53
3.2.1 简介 53
3.2.2 注册窗口类 53
3.2.3 创建窗口 55
3.2.4 显示和更新窗口 56
3.2.5 消息循环 57
3.3 消息处理函数 58
3.4 常用Windows函数 59
3.4.1 显示对话框 59
3.4.2 定时器 59
3.4.3 得到时间 60
3.4.4 播放声音 60
第四章 描绘游戏的画笔 61
4.1 初始化DirectDraw 61
4.1.1 简介 61
4.1.2 DirectDraw对象 62
4.1.3 设置控制级和显示模式 63
4.1.4 创建页面 64
4.2 后台缓存和换页 66
4.3 调入图像 67
4.4 页面的丢失与恢复 67
4.5 透明色 68
4.6 图像传送 68
4.7 程序实例 72
4.8 图像缩放 72
4.9 释放DirectDraw对象 72
第五章 丰富画面的技巧 74
5.1 填涂颜色 74
5.2 输出文字 75
5.3 GDI作图 75
5.4 程序实例 76
5.5 锁定页面 76
5.6 程序提速 78
5.7 特殊效果 82
5.7.1 减暗和加亮 82
5.7.2 淡入淡出 83
5.7.3 半透明 83
5.7.4 光照 84
5.7.5 动态光照 85
5.7.6 光照系统 88
5.7.7 天气效果 88
第六章 加速游戏的魔法 89
6.1 内嵌汇编简介 89
6.2 基本指令 90
6.3 算术指令 91
6.4 逻辑与移位指令 93
6.5 比较、测试、转移与循环指令 93
6.6 MMX指令集之基本指令 96
6.7 MMX指令集之算术与比较指令 98
6.8 MMX指令集之逻辑与移位指令 99
6.9 MMX指令集之格式调整指令 100
第七章 我没有想好名字 102
7.1 读取键盘数据 102
7.2 读取鼠标数据 103
7.3 恢复和关闭DirectInput 104
7.3.1 恢复DirectInput设备 104
7.3.2 关闭DirectInput 104
7.4 初始化和关闭DirectX Audio 104
7.4.1 初始化DirectX Audio 104
7.4.2 关闭DirectX Audio 105
7.5 播放MIDI和WAV音乐 105
7.5.1 调入MIDI和WAV文件 105
7.5.2 播放MIDI和WAV文件 106
7.5.3 停止播放 107
7.6 在3D空间中播放音乐 107
7.7 播放MP3音乐 109
7.7.1 调入MP3文件 109
7.7.2 播放MP3文件 109
7.7.3 停止播放和释放对象 110
第八章 支撑游戏的基石 111
8.1 链表 111
8.2 哈希表 111
8.3 快速排序 112
8.4 深度优先搜索 113
8.5 广度优先搜索 117
8.6 启发式搜索 120
8.7 动态规划 126
8.8 神经网络 128
8.9 遗传规划 129
第九章 向三维世界迈进 131
9.1 概述 131
9.2 基本知识 133
9.2.1 初始化DXGraphics 133
9.2.2 关闭DXGraphics 135
9.2.3 恢复DXGraphics设备 135
9.3 设置场景 135
9.3.1 设置渲染状态 135
9.3.2 设置矩阵 136
9.4 创建场景 137
9.4.1 调入3D场景 138
9.4.2 调入2D图像 139
9.5 刷新场景 140
9.6 渲染场景 141
9.6.1 渲染3D场景 141
9.6.2 渲染2D图像 141
9.7 改变场景 141
9.8 显示文字 142
9.9 程序实例 143
第十章 我没有想好名字 144
10.1 灯光 144
10.2 半透明 145
10.3 纹理混合 146
10.4 雾 148
10.5 凹凸贴图与环境贴图 149
10.6 粒子系统 149
10.7 骨骼动画 149
10.8 镜子 151
10.9 影子 151
第十一章 我没有想好名字 152
11.1 基本概念 152
11.2 程序流程 152
11.2.1 服务器端 152
11.2.2 客户端 153
11.3 程序实例 153
11.4 错误处理 158
11.5 显示IP地址 158
11.6 更有效地传送数据 159
第十二章 创造我们的世界 161
12.1 程序流程 161
12.2 程序结构 162
12.3 基本方法 163
12.4 SLG编程要点 163
12.4.1 电脑AI 163
12.5 RPG & ARPG编程要点 163
12.5.1 迷宫的生成 163
12.5.2 脚本技术 163
12.6 RTS编程要点 163
12.6.1 寻路 163
12.6.2 电脑AI 163
12.7 FPS编程要点 164
12.7.1 移动 164
12.7.2 碰撞检测 164
12.8 游戏中的物理学 165
附 录 166
附录一 Windows常见消息列表 166
附录二 虚拟键列表 171
Windows消息中的虚拟键 171
DirectInput中的虚拟键 172
附录三 DirectX函数返回值列表 174
DirectDraw部分 174
Direct3D部分 181
附录四 Winsock函数返回值列表 183
附录五 游戏编程常用网址 187
附录六 中英文名词对照 188
附录七 常见问题及解决办法 189
1. 程序编译时出现"Warning" 189
2. "Cannot Execute Program" 189
3. "Unresolved External Symbol" 189
4. 运行时出错 189
5. 大家还有什么问题,可以告诉我 189

 楼主| 发表于 2006-1-5 14:47:26 | 显示全部楼层

想必大家都听说过“计算机语言”吧,我们就要靠它来向计算机表述我们的游戏到底是怎样的----这个过程就是所谓“编程”。由于游戏对速度的要求较高,过去我们一般使用C语言,因为用它编制的程序不仅执行速度快,还可以充分地使用硬件的各种资源。而现在(不过也是十多年前的事了J)有了C++语言,它是对C语言的重大改进。C++语言的最大特点是提供了“类” ,成为了“面向对象”的语言。关于此,我们会在第二章详细介绍。本章将先介绍一些游戏编程所必需的C++语言基础知识。最后需要提醒大家的是,在学习本章时最好边学边实践,自己试着写写C++程序。

1.1 VC.net概述

在切入C++语言之前,我们有必要简略地介绍一下VC.net的基本使用方法。首先当然是安装VC.net,值得注意的是,VC.net中附带的DirectX SDK并不是8.1最终版,推荐访问http://www.microsoft.com/msdownload/platformsdk/sdkupdate/更新之。然后启动VC.net,会看见一个"Start Page",在"Profile"一栏选择"Visual C++ Developer"。第二步是转到"Get Started"一栏,选择"New Project",并在出现的窗口选择"Visual C++ Projects"一栏中的"Win32 Project",填好"Name"和"Location",按"OK",这时会出现一个"Win32 Application Wizard"。此时需要在"Application Settings"一栏把"Empty project"前的方框勾上,以防VC.net在工程中加入一些无意义的垃圾;如果想编DOS窗口下的程序,例如这一章和下一章的程序,还要把"Console Application"选上。最后按"Finish"就完成了工程的创建。

1.1 Start Page

在屏幕的左边,我们可以看到出现了"Solution Explorer"和"Dynamic Help"两栏,其中"Solution Explorer"又可变为"Class View"或"Resource View",其内容分别为:工程中包含有什么文件,工程中的类、变量和函数的结构,工程中包含有什么资源。至于"Dynamic Help"就是动态帮助,非常方便。

大家会注意到现在工程还没有文件,所以接下去我们需要学习如何新建一个文件,如果你想新建的文件是C++程序文件(.cpp),那么应该在"Source Files"上按右键,选择"Add" --- "Add New Item",在出现的窗口中选择"C++ File",定好名字,再按"Open"即可(假如你加入的文件叫"Haha.cpp",Solution Explorer将如右图所示);如果你想新建的文件是头文件(现在先不要管头文件是什么),在"Header Files"按右键,也选择"Add" --- "Add New Item",在出现的窗口中选择"Header File",也定好名字并按"Open"就行了。

在工具栏的中部可以更改程序的模式:Debug或是Release。在一般情况下,建议大家选择Release,可以减少文件大小并增加运行速度;而在调试程序时,必须选择Debug。在默认的情况下,编译好的程序会相应的放在工程目录下的"Debug"或"Release"子目录内。

最后我们来看看一个重要的操作,即如何把LIB文件(现在也不要管LIB文件是什么…)加入工程中:首先在"Solution Explorer"窗口中找到工程名,然后在上面按右

图1.2 键并选择"Properties",在出现的窗口中选择"Linker" --- "Input" --- "Additional Dependencies",最后填上要加入的LIB文件名即可。

 楼主| 发表于 2006-1-5 14:48:50 | 显示全部楼层

OK,下面让我们看一个简单的C++程序:>>

/*------------------------------------------------->>

First C++ Program>>

--------------------------------------------------*/>>

> >

#include <iostream> //现在你只需知道要使用输入输出语句就必须写这行。

//这一行结尾不用加分号因为它不是真正的C++语句。

//1.3节将会对此做出解释。

using namespace std; //这是什么意思呢1.2.3节会有解释。

int a=5; //声明变量a,同时顺便赋5给它。C++中变量都需先声明后使用。

//int说明了a的数据类型为整数。

int square(int x); //声明函数square,它有一个参数,为int类型,即整数。返回值也

//int类型。C++中的函数都需要先声明后给出定义。

int square(int x) //函数真正定义。

{

return x*

x; //返回x*x,可以在一个语句中的间隔位置插入回车将其分成几行。

}

int main( ) //主函数,每个DOS窗口下的C++程序都需要它

{

int A; //声明变量AC++中变量声明的位置是比较随意的。

cout<<"请输入A:"; //输出"请输入A:",箭头的方向很直观。

cin>>A; //输入A, 注意箭头方向的更改。

cout<<"A="<<A<<endl; //依次输出"A="A的值并换行。endl代表换行。

cout<<"a+A="<<a+A;

cout<<"A*A="<<square(A);

return 0; //可以指示程序正常结束

}

运行程序的方法是按工具栏中部的小三角形。

从这个程序中,我们可以看到C++程序的一些特点:

(1) 区分大小写,关键字全部是小写的。

(2) 每一行语句都需要用";"结尾。

(3) 用"{"和"}"括起的句被称为块语句,形式上被认为是一个语句(就像PASCAL中的begin和end)。

(4) "//"至行尾为注释,"/*"至"*/"中全为注释,它们不会被编译。

(5) 主体是由一个个函数所构成的。在1.6节将会详细地介绍函数。

1.2 入门知识

1.2.1 数与数据类型

对于十进制数的表示,C++与其它语言一致,同样可以使用科学记数法,如3.145e-4。在C++中还可以直接表示十六进制数,只要在前面加上"0x"即可。如0x23。如果要表示的是负十六进制数,可以直接在"0x"前加上负号。为了清楚说明一个数是float类型,我们可以在数的结尾加上"f",例如1.00f。否则,该数默认为double类型。

下面我们来看看C++中的基本数据类型:

bool(逻辑型) char(字符或8位整数) short(16位整数)

int(16位或32位整数) long(32位整数) float(32位浮点数)

double(64位浮点数) long double (80位浮点数)

bool类型用true和false代表真与假,其实际占用空间是8位。

某一char类型的变量如果等于'a'(注意C++中字符用单引号,字符串用双引号),则它又等于a的ASCII码,即97。依此类推。

int类型在DOS下一般为16位,在WINDOWS下一般为32位,如果想保险一点自己试试就知道了。

在整数数据类型前可加上"unsigned"表示为无符号数,数的范围可增大一倍。比如说char类型数据的范围是-128到127,unsigned char类型数据的范围则为0到255。

使用sizeof( )可以得到任何对象占用的字节数,例如如果有一个char类型的变量a, 则sizeof(a)会返回1。

有的类型之间是可以自动转换的,如可以把一个float类型的变量的值赋给一个int类型的变量,小数点后的部分将会被自动截掉。如果不放心可使用强制类型转换,形式为(目标类型)变量名。比如说如果有一个char类型的变量 c值为'b',直接输出c会得到'b'这个字符但输出(int)c会得到'b'的ASCII码。强制类型转换不会改变变量的值(除非将一个浮点数转换为整数等情况),它只是返回转换后的值。注意字符串和整数之间不能用强制类型转换实现转换,办法在1.9节。

我们还可以借助typedef定义自己的数据类型,例如typedef myint unsigned int;后myint就等价于unsigned int。VC.net系统已经预先用typedef定义好了不少类型,例如BYTE等价于unsigned char,WORD等价于unsigned short,DWORD 等价于unsigned long等等。

1.2.2 变量与常量

C++中的变量几乎可在任何地方处定义,而且可以同时定义多个变量,如int a,b;。但每一个变量只在最紧挨它的一对{和}符号内起作用,只有在所有函数之外定义的变量才为全局变量,即在整个cpp文件中有效。如果局部变量和全局变量重名,调用时会使用局部变量,如果一定要使用那个全局变量,调用时在变量名前加上"::"即可。这里建议大家尽量少用全局变量,因为它可能使程序变得混乱和难于调试。

所有变量定义的前面均可加上修饰符"const"表示它是常量,不能在程序中改变它的值。其实如果我们不打算在程序中改变某变量的值,我们就可以把它声明为常量以防止意外改动。我们还可加上修饰符"static"表示此变量是静态变量,这个要举一个例子以方便说明:比如说在某一个函数内有这样一条定义:static int count=0; ,那么程序执行前就会为count这个变量开辟一块固定的空间并把count的初值设为0。以后每次执行这个函数时,程序不会象普通变量那样重新为它分配空间,也就是不会改变它的位置和数值,换句话说,它的生命周期与整个程序一样。这样只要在函数中再加一句count=count+1即可统计这个函数执行了多少次。

1.2.3 Namespace

Namespace是一个挺有趣的东西,它的引入是为了方便我们使用相同名字的变量、常量、类(在第二章我们会接触类)或是函数。一个Namespace是这样定义的:

namespace xxx //xxxnamespace的名字

{

在这里可以像平常一样定义各种东西

}

以后要使用某个namespace中的东西,比如说xxx中的aaa,像这样:xxx::aaa即可。不过这样好像挺麻烦的----平白无故就多出了一个"xxx::"。于是有了"using namespace xxx;"这种语句,可以帮你省下这几个字符。记住,"using namespace"也只是在最紧挨它的一对{和}符号内起作用,在所有函数之外执行的这条语句才在整个文件中有效。注意:

namespace s1

{

int a=0;

}

namespace s2

{

float a=0;

}

void main( )

{

using namespace s1;

using namespace s2;

//a=a+1; //这句是错误的!因为编译器此时无法确定a在哪个namespace

s1::a = s2::a + 1; //这样就是正确的

}

那么我们在第一个程序中为何要using namespace std;呢?其实也是为了把"std::cout"变成简洁一点的"cout"。请看1.3节。

 楼主| 发表于 2006-1-6 11:17:30 | 显示全部楼层

1.2.4 操作符与表达式>>

最后要说的就是C++中的操作符和表达式,与其它语言相同的就不在此赘述,讲讲一些与其它语言不同的内容:>>

%为取余数,比如说20%3=2。>>

在逻辑表达式中,用==表示相等,!=表示不等,比如说(4==5)为FALSE;大于等于用>=表示,小于等于则是<=&&表示逻辑与,||表示逻辑或,!表示逻辑非。例如如果a=8,则( (a!=9) && ( (a==3) || (a==6) ) )为false。>>

<<(左移)和>>(右移)非常好用,作用是把这个数的二进制形式向左或右移位(cin和cout中的<<和>>被使用了运算符重载,所以意义不同,具体可参阅2.4节),举两个例子也许会好说明些:>>

18(二进制形式为0010010)<<2得到72(二进制形式为1001000)

77(二进制形式为1001101)>>3得到9(二进制形式为0001001)

我们可以看到,左移和右移可以代替乘或除2的n次方的作用,而且这样做可以节省不少CPU运算时间。在程序优化中这一种方法是十分重要的,例如a*9可用(a<<3)+a代替(注意,"+"运算比"<<"运算优先)。

C++还提供了算术与&、算术或|、算术非~,算术异或^等重要的二进制运算。比如25(11001)^17(10001)等于8(01000)。这些运算都是逐位对二进制数进行的。0&0=0, 0&1=0, 1&0=0, 1&1=1; 0|0=0, 0|1=1, 1|0=1, 1|1=1; ~0=1, ~1=0; 0^0=0, 0^1=1, 1^0=1, 1^1=0。

++/--操作符,即自增1/自减1,是C++的特色之一。a=7; a++; 则a变为8。(C++语言岂不是成了D语言?J) 注意a++和++a不同:++a是先自增后给值,a++是先给值后自增:若a=12,(a++)+5为17,(++a)+5却为18,不过a后来都变成了13。

最后要说的是一个很有趣的操作符,就是"?:",它可以在一定程度上代替if语句的作用,因为"A?B:C"等价于"if A then 返回B else 返回 C"。举一个例子,(a>b)?a:b可返回a和b中的较大者。

值得注意的是由于C++的操作符众多,所以运算先后次序较复杂,如果没有注意到这一点而少加了几个括号将出现出人意料的结果。下面按优先级高到低列出了C++中的操作符:

1. ()(小括号) [](数组下标) .(类的成员) ->(指向的类的成员)

2. !(逻辑非) .(位取反) -(负号) ++(加1) --(减1) &(变量地址)

3. *(指针所指内容) sizeof(长度计算)

4. *(乘) /(除) %(取模)

5. +(加) -(减)

6. <<(位左移) >> (位右移)

7. < (小于) <= (小于等于) > (大于) >= (大于等于)

8. == (等于) != (不等于)

9. & (位与)

10. ^ (位异或)

11. | (位或)

12. && (逻辑与)

13. || (逻辑或)

14. ? : (?表达式)

15. = += -=(联合操作)

在表达式方面,C++基本与其它语言相同。只是C++为了简化程序,提供了联合操作:"左值 操作符=表达式"等价于"左值= 左值 操作符 表达式"。例如a*=6+b等价于a=a*(6+b),c+=8等价于c=c+8。

在C++中,所有表达式都有返回值。一般来说,(左值 操作符 右值)表达式的返回值与右值相同;条件表达式如(a>b)的返回值在条件成立时为1,不成立时为0。

1.3 预编译指令

现在该解释在第一个例子中#include <iostream>的意义了,其实这句是预编译指令。预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。常见的预编译指令有:

(1)#include <xxx.xxx>指令

该指令指示编译器将xxx.xxx文件的全部内容插入此处。若用<>括起文件则在系统的INCLUDE目录中寻找文件,若用" "括起文件则在当前目录中寻找文件。一般来说,该文件后缀名都为"h"或"hpp",被称为头文件,其中主要内容为各种东西的声明。

那么为什么在第一个程序中我们可以省略"iostream.h"的".h"呢(大家可以自己找找,会发现并没有一个叫iostream的文件)?这有一个小故事。当初ANSI在规范化C++的时候对iostream.h进行了一些修改,比如说吧其中的所有东西放进了一个叫std的namespace里(还有许多头件都被这样修改了)。但是程序员就不答应了,因为这意味着他们的程序都要被修改才能适应新编译器。于是ANSI只好保留了对原来调用iostream.h的方法(#include <iostream.h>)的支持,并把调用新的iostream.h的方法修改成现在的样子(#include <iostream>)。

言归正传,我们#include <iostream>之后编译器会看到iostream.h中对输入输出函数的声明,于是知道你要使用这些函数,就会将包含有输入输出函数定义的库文件与编译好的你的程序连接,形成可执行程序。

注意<>不会在当前目录下搜索头文件,如果我们不用<>而用""把头文件名扩起,其意义为在先在当前目录下搜索头文件,再在系统默认目录下搜索。

(2)#define指令

该指令有三种用法,第一种是定义标识,标识有效范围为整个程序,形如#define XXX,常与#if配合使用;第二种是定义常数,如#define max_sprite 100,则max_sprite代表100(建议大家尽量使用const定义常数);第三种是定义"函数",如#define get_max(a, b) ((a)>(b)?(a)b)) 则以后使用get_max(x,y)可得到x和y中大者(这种方法存在一些弊病,例如get_max(a++, b)时,a++会被执行多少次取决于a和b的大小!所以建议大家还是用内联函数而不是这种方法提高速度。关于函数,请参阅1.6节。不过这种方法的确非常灵活,因为a和b可以是各种数据类型,这个特点我们可以换用2.7节介绍的模板实现)。

(3)#if#else#endif指令

这些指令一般这样配合使用:

#if defined(标识) //如果定义了标识

要执行的指令

#else

要执行的指令

#endif

在头文件中为了避免重复调用(比如说两个头文件互相包含对方),常采用这样的结构:

#if !(defined XXX) //XXX为一个在你的程序中唯一的标识符,

//每个头文件的标识符都不应相同。

//起标识符的常见方法是若头文件名为"abc.h"

//则标识为"abc_h"

#define XXX

真正的内容,如函数声明之类

#endif

 楼主| 发表于 2006-1-6 11:19:05 | 显示全部楼层

1.4 结构,联合和枚举>>

1.4.1 结构>>

结构可以把不同变量变为一个变量的成员。例如:>>

struct S //定义结构S>>

{>>

short hi; //结构S的第一个成员

short lo; //结构S的第二个成员

};

S s; //定义S类型的变量s

然后我们就可以像s.hi和s.lo一样使用s的成员。

1.4.2 联合

联合可以让不同变量共享相同的一块空间,举个例子:

#include <iostream>

using namespace std;

struct S

{

short hi;

short lo;

};

union MIX

{

long l;

S s;

char c[4];

};

void main ( )

{

MIX mix;

mix.l=100000;

cout<<mix.s.hi<<endl;

cout<<mix.s.lo;

}

此时mix所在的内存位置的情况是这样的:

OCK aspectratio="t" v:ext="edit">OCK>

1.2

1.4.3 枚举

枚举的用处是迅速定义大量常量。例如:

enum YEAR //可以给枚举起一个名字

{

january=1, //如果不加上"=1"月份将依次为0-11,不符合我们平时的习惯,所以

//可以加上它。

february,

march,

april,

may,

june,

july,

august,

september,

october,

november,

december

};

 楼主| 发表于 2006-1-6 11:20:58 | 显示全部楼层

1.5 控制语句>>

C++中的控制语句格式简洁且功能强大,充分证明了它是程序员的语言。>>

> >

1.5.1 判断和跳转语句>>

C++中的判断语句格式如下:>>

if (条件) 真时执行语句; else假时执行语句;

例如:

if (a>=9) a++; else a--;

值得注意的是C++中的“真”与“假”的意义就是这条表达式不为0 还是为0。比如if (a-b) do_stuff; 的作用与 if (a!=b) do_stuff; 相同。

臭名昭著的跳转语句(不过有时候你还是不得不用)则是这样的:

标号:语句;(一般来说标号用"_"开头)

goto标号;

举个例子方便大家理解:

#include <iostream>

using namespace std;

void main( )

{

int target=245; int a;

cout<<"欢迎您玩这个无聊的猜数游戏"<<endl;

cout<<"您的目标是猜中我想好的数"<<endl;

cout<<"请输入第一次猜的数:";

_input: cin>>a;

if (a>target)

{

cout<<"您刚才输入的数太大了!"<<endl;

cout<<"";

goto _input;

}

else if (a<target)

{

cout<<"您刚才输入的数太小了!"<<endl;

cout<<"再猜一次:";

goto _input;

}

else

cout<<"恭喜你,猜对了!"<<endl;

}

1.5.2 选择语句

C++中的选择语句很灵活,我们先看看与其它高级语言相似的形式:

switch (变量)

{

case常量/常数1:

语句;//注意,这里可有多个语句且不需用{ }括起,不过其中不能定义变量。

break; //为什么要加这一句呢?下面会解释。

case常量/常数2:

语句;

break;

……

case 常量/常数n:

语句;

break;

default: //如所有条件都不满足则执行这里的语句。

语句;//这后面就没有必要加break;了。

}

break的作用其实是防止继续执行后面的语句,试试下面的程序:

#include <iostream>

using namespace std;

const aaa=5;

void main( )

{

int a;

cin>>a;

switch(a)

{

case 0:

cout<<endl<<"您输入的是0";

case 3:

cout<<endl<<"您输入的是3";

case aaa:

cout<<endl<<"您输入的数与aaa相等";

default:

cout<<endl<<"???";

}

}

按照一般人的想法,当你输入0、2、3、5时会分别得到"您输入的是0"、 "???"、 "您输入的是3"、 "您输入的数与aaa相等",不过你可以试试结果是否真的是这样。试完后,你可以加上一些break再看看结果又将是怎样。

1.5.3 循环语句

先介绍while循环语句,共有两种形式:第一种是while (条件) 语句,意义为先判断条件是否满足,如果满足则执行语句(否则退出循环),然后重复这个过程。第二种形式是do 语句 while (条件),意义为先执行语句再判断条件,如果条件成立则继续执行语句(不成立就退出循环),这个过程也会不断重复下去。例如while((x+=1)=y);语句可以使x不断加1直到变成与y的值相同。

然后就是C++最强大的for循环,它的形式如下:

for (语句1;条件;语句2) 语句3 (其中任何一部分都可省略)

看上去好像很古怪,其实它就等价于这样:

语句1;

while (条件)

{

语句3;

语句2;

}

比如for (i=1;i<=100;i++) cout<<i<<endl; 结果将会是输出1到100的数。

又比如for (cin<<i; i<=0; ;) cin<<i;将会不断输入i至i大于0为止(省略了语句2)。

for (;;);将会陷入死循环,注意它比while(1);执行速度快。

在循环语句中可顺便定义变量,如for (int i=1;i<=100;i++) cout<<i<<endl;

有时我们需在循环中途跳至循环外,此时break又可以派上用场了。有时又需要在循环中途跳至下一次循环,continue可以帮你这个忙。

 楼主| 发表于 2006-1-6 11:21:22 | 显示全部楼层

1.6 函数>>

C++中的函数是这样定义的:>>

返回值数据类型 函数名(参数表)>>

{>>

语句;>>

}

例如:

int fun(int x, int y)

{

x=x+1; //注意这一句只能在函数内改变x的值,请参阅下文

return x*y; //返回x*y,并会立刻退出该函数

}

当返回值数据类型为void时表示无返回值,就像其它语言中的“过程”。

参数表中在不引起歧义的情况下可有缺省值,例如void xyz(int a, int b=0); (只需在声明函数时说明缺省值),则xyz(12)等价于xyz(12,0)。

在main函数开始前最好声明一下程序中的函数(main函数不必声明),声明格式为:

返回值的数据类型 函数名(参数表); (注意有一个分号)

在声明的参数表里可以省略变量名,例如void myfunc(int,float);

在函数的定义(而不是声明)的最前面加上"inline"说明其为内联函数可提高一点速度,但增大了文件的大小。

就象其它语言一样,C++中的函数可以递归调用(自己调用自己)。它还有一个区别于其它语言的重要特性----可以"重载",例如如果有这样两个函数:

float fun(float x)

{

return x*x*x;

}

int fun(int x)

{

return x*x;

}

假设a为4,那么如果a为一个float类型变量,fun(a)会返回64;但若a为int类型,fun(a)会返回16。可以想像,这个特性在实际编程中将十分有用。

下面我们再看看一个问题:有人想编一个交换a和b的函数,于是他这样写:

void swap(int a, int b)

{

int t=a;

a=b;

b=t;

cout<<"a="<<a<<" b="<<b;

}

在函数中的输出是正常的,但运行完后a和b并没有被交换,你知道这是为什么吗?其实原因在于:在默认状况下函数的参数都为形式参数,函数所得到的所谓参数只是一个和参数的类型、数值都相同,但在内存中的位置不同的东西,也就是说,编译器把所有参数都复制了一份。所以这段程序并不能真正地改变这两个变量的值。正确的解决方案是使用实参,即在参数名前加上"&" (当你看完1.7.1节后你将会对此有更深的理解),就象下面的函数那样:

void swap(int &a, int &b)

{

int t=a;

a=b;

b=t;

}

在默认情况下,函数的返回值也只是一个复制品,如果你一定要让它返回真正的东西,可以像这样写函数:int &foo(){do_something;}。不过注意在1.7.1节中说明的限制----我们不能这样返回在函数中创建的变量。

下面举一个使用了函数的程序例子(比较无聊J):

#include <iostream>

using namespace std;

float pi=3.14159;

float s_circle(float r);

float v_cylinder(float r, float h);

float v_cone(float r, float h);

float v_all(float stop, float smiddle, float sbottom,float h);

float v_all(float stop, float smiddle, float sbottom,float h)

{

return (stop+4*smiddle+sbottom)*h/6;

}

float v_cone(float r, float h)

{

return s_circle(r)*h/3;

}

float v_cylinder(float r, float h)

{

return s_circle(r)*h;

}

float s_circle(float r)

{

return pi*r*r;

}

void main( )

{

float r,h;

float st,sm,sb;

cout<<"这个十分无趣的程序会帮您计算一些几何体的体积"<<endl;

cout<<endl<<"0代表要计算圆锥体"<<endl;

cout<<"1代表要计算圆柱体"<<endl;

cout<<"2代表要计算拟柱体"<<endl;

cout<<"请选择:";

int choice;

cin>>choice;

cout<<endl;

switch(choice)

{

case 0:

cout<<"底面半径=?";

cin>>r;

cout<<"=?";

cin>>h;

cout<<endl<<"体积="<<v_cone(r,h);

break;

case 1:

cout<<"底面半径=?";

cin>>r;

cout<<"=?";

cin>>h;

cout<<endl<<"体积="<<v_cylinder(r,h);

break;

case 2:

cout<<"上表面的面积=?";

cin>>st;

cout<<"中截面的面积=?";

cin>>sm;

cout<<"下表面的面积=?";

cin>>sb;

cout<<"=?";

cin>>h;

cout<<endl<<"体积="<<v_all(st,sm,sb,h);

break;

}

}

 楼主| 发表于 2006-1-6 11:22:17 | 显示全部楼层

1.7 指针、数组与字符串>>

1.7.1 指针>>

指针是C++ 强大功能的体现,也是一个令人又爱又恨的东西。>>

所谓指针,顾名思义,就是一个保存着某变量在内存中的位置的变量。指针的定义是这样的:数据类型 *指针名; 其中数据类型这一项为指针所指向的变量的类型。为什么一定要说明类型呢?因为指针中只储存了变量的地址,没有储存变量占用了多少内存空间。如果这样定义指针:void *p; 则指针可以指向任何类型的变量。注意,如果你这样写:int* a,b;,只有a是指针;应该int *a,*b;才能使a和b都成为指针。>>

下面要说说两个操作符,即&*。&的作用是取变量的地址。而*则是&的逆运算,即取位于该地址的变量的值。所以我们用p=&a即可把p指向a,执行后*p就完全等价于a,比如用*p=(*p)+2可将a的值加上2。图1.2显示了当p指向一个字符串"Hi!"后这两个操作符的工作。

1.3>>

需注意的是,如果没有把一个有效的地址给指针,却直接修改指针所指向的内容,会出现不可预料的结果。很多时候程序出错就是由此引起的。

事实上,C++中还存在一种与指针有点类似的东西----引用,它的作用是为变量提供一个"假名"。它的定义方法是数据类型 &假名=某变量;(由于引用必须指向一个东西,所以定义时必须同时给出其指向),其实我们在讨论实参时已经接触到了它(当然,我们也可以通过传递指针实现实参,它们编译后的代码是一样的,只是我们调用时多加一个&而已)。执行完后,假名就会完全等价于原变量且不可再改变它的指向。

这里顺便提醒一下,我们绝不应该将在函数中创建的变量的地址或引用返回。因为在退出一个函数时,在函数体中所创建的所有变量都将被销毁,所以虽然地址是可以传回去,但它所指向的内容已经毫无意义。那么指针呢?在函数里new的指针能传回去吗?答案是可以,但是,你必须为极有可能发生的内存泄漏负责,因为要把这些指针找出来一个个delete掉实在很麻烦。“我们可以试试返回静态变量的地址或引用”,有的人会这样想。这在大多数情况下是个好办法,但是仍然存在可能的漏洞----因为这个静态变量的地址由始到终都是不变的。如下:

int &foo(int a)

{

static int t;

t=a;

return t;

}

int main()

{

if ( foo(1) == foo(2) ) //这个条件将会成立!

}

指针有什么用呢?第一个用处是可以动态分配大量内存。我们知道DOS下很多语言对数组的大小有很严格的限制,但C++ 却可以开辟非常大的数组,而且可以用完就释放内存,这就是指针的功劳。具体会在介绍数组时介绍。

我们还可以创建函数指针,这也是C++的特色之一。所谓函数指针,顾名思义就是指向一个函数的指针。举个例子,如果有一些函数:

float aaa ( int p );

float bbb ( int q );

float ccc ( int r );

那么我们可以这样定义一个函数指针:float (*p) (int);

这时就可以将p指向上面的各个函数,如p = bbb;执行后p(100);就等价于bbb(100);

如果在某一段程序中需要根据情况(例如某变量的值)调用aaa、bbb,ccc函数,那么我们可以不必使用烦琐的switch,只需使用函数指针数组即可,非常方便。

顺便说一下这时应该如何用typedef定义p的数据类型:typedef float(*pFunction)(int);后直接用pFunction p;即可定义上面的那个指针p。

 楼主| 发表于 2006-1-6 11:23:03 | 显示全部楼层

1.7.2 数组>>

C++中的数组和指针有着千丝万缕的联系。象其它语言一样,C++可以直接定义数组,如int a[100]; 即可定义一个由100个char类型变量组成的数组;也可以在定义时顺便赋值,例如char b[5]={'a', 'b', 'c', 'f', 'z'};;还可以定义高维数组,如char c[200][50];(相当于BASIC中的c(200, 50))。使用数组时要注意几点:>>

(1)数组的下标是从0开始的,上面所定义的a数组的下标范围为0到99,刚好是100个元素。>>

(2)数组越界不会有任何提示。>>

(3)数组需要你自己清零。>>

如果你使用直接定义的方法产生数组,还需注意下面两点:

(1)数组的大小必须是常数或常量,象int a; int b[a];这样是错误的。

(2)你得到的实际上是一个特殊的与"数组名"同名的指针。

第二点也许有些费解,你可以试试这段程序就会明白:

#include <iostream>

using namespace std;

void main( )

{

int abc[1000]={0}; //这样就可以使数组被预先清0

//注意int abc[1000]={1};会使abc[0]=1而其它元素=0

abc[0]=987;

cout<<*abc<<endl;

*abc=787;

cout<<abc[0];

}

我们还可以直接使用指针创建数组。比如说我们要临时分配一块空间,存储100000个int类型数据,那么就可以这样做:int *p; p=new int[100000];(你可以将其合并为int *p=new int[100000],在这里又出现了一个新操作符"new"),则系统会在内存找到一块足够大的空闲空间,再将p指向这块空间的起始位置,以后就可以把p当成一个数组来使用了。这种办法的第一个好处是用完这块内存后可以释放内存(其实你应该永远这样做,否则会造成所谓Memory Leak,即内存资源泄漏),就象这样即可:delete[ ] p;"delete[ ]"也是C++中的操作符);第二个好处是可以动态定义数组,例如:

int a; cin<<a; int *p=new int[a];

所以,建议大家使用new来创建数组。

不过直接使用刚才用来创建数组的指针并不方便(试试p=&p[100]能实现把p指向p[100]吗?可能会死机!),最灵活的办法是把一个另外的指针指向数组的元素,因为指针可以进行加减运算。比如说如果p=a[0],执行p+=46; 即可使p指向a[46],再执行p--;则p指向a[45]。看看下面的例子:

#include <iostream>

using namespace std;

void main( )

{

int *p,*q;

p=new int[100000];

q=&p[0];

for (int i=0;i<100000;i++)

*(q++)=0; //这样也可以清零

q=&p[1];

*q=128; //p[1]变成128

cout<<p[1];

delete[ ] p; //删除数组用delete[ ]

delete q; //删除指针用delete

//要养成用完指针就释放的良好习惯

}

有时候你可能会忘记已经释放了一个指针,仍去使用它,结果会出现不可预料的结果。为了防止出现这种情况,你可以在释放完指针后再把它设为NULL以保证它不被继续使用。我们可以用这样两个宏:

#define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } }

#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p)=NULL; } }

下面还要讲讲使用指针创建高维数组的方法,因为此时要用到指针的指针(指针也是变量,也要占内存,所以也有自己的地址)甚至指针的指针的指针的……(啊!有一位听众晕倒了!谁抬他出去?)下面的一段程序演示了如何创建一个高维数组p[40][60](比较难懂,做好心理准备):

int **p; //指向指针的指针!

p=new int *[40]; //执行完后p就是一个元素为指针的数组!

//可以将这句与 p=new int[40]; 对照着想想

for (int i=0;i<40;i++)

p=new int[60]; //p数组中的每一指针分配内存,将其也变为一个个数组

下面是一个二维数组p[n][m]的结构:

1.4

如果你弄懂了上面的程序,你就可以再玩点新花样:定义不对称数组。比如这样:

int **p; *p=new int *[10];

for (int i=0;i<10;i++)

p=new int[i+1];

1.7.3 字符串

C++中的字符串其实也是指针的一种,因为并没有一种基本数据类型是字符串,所谓字符串实际是一个以"/0"(这叫做转义符,代表一个ASCII码为0的符号)作为结束标志的一个字符指针(char *),它实际上是一个字符数组,就像图1.2中那样。所以如果有一个字符串s为"abc",实际上它为"abc/0",sizeof(s)会返回4,而不是3。定义字符数组时也要记住多留一位。

 楼主| 发表于 2006-1-6 11:25:08 | 显示全部楼层

一般是用字符指针的方法定义字符串的:char* str = "muhahaha";,但我们知道使用指针前一定要先找好初地址,所以事实上执行的是将str指向一个const char[]。由于这里有个const,我们没有必要用delete[]释放这个字符指针。用过BASIC的人要注意C++中的字符串并不能比较(用==比较两个指针时它只会比较两个指针的所指向的地址是否相同)、相互赋值、相加和相减,这些操作一般是靠使用系统提供的字符串操作函数实现的,请参阅1.9节。请特别注意字符串不能相互赋值,请看下面一段代码:>>

char *str="aaaa";>>

char *str1="oh";>>

str1=str; //!!!>>

cout<<str1;>>

输出很正常,似乎我们实现了拷贝字符串的目的。然而仔细想一想,把一个指针赋给另一个指针时到底会发生什么?假设str指针本来指向地址0x0048d0c0,而str1指针指向0x0048d0bc,那么执行str1=str;后两指针将同时指向地址0x0048d0c0!C++并不会为了字符串搞特殊化,指针的赋值操作只会简单地拷贝地址,而不是拷贝内容。要拷贝内容,还得靠1.9节介绍的strcpy( )。

1.7.4 小结

学了这么多C++知识,大家是不是有点疲倦了呢?如果你想兴奋一下,那么就看看下面这段程序吧,它可以输出π的前781位。

#include <iostream>

using namespace std;

long a=10000,b=0,c=2800,d,e=0,f[2801],g;

void main()

{

for(;b-c;)f[b++]=a/5;

for(;d=0,g=c*2;c-=14,cout<<e+d/a,e=d%a)

for(b=c;d+=f*a,f=d%--g,d/=g--,--b;d*=b);

}

程序使用了C++提供的所有能简化代码的手段,包括前面没提到的逗号(可以将几个语句硬拼在一起,返回值以最靠右的语句为准)。它表现出来的数学功底是很惊人的,值得大家研究研究。当然,不提倡写这样费解的代码!

1.8 多文件程序的结构

记得以前我第一次使用Visual C++编游戏的时候,由于当时对C++还不是很熟,调试了很久都没有成功。后来把程序email给了一位高手叫他看看问题在哪里,过了几天他把程序送回来时已经可以运行了,原来他在我的头文件中声明变量的语句前都加了一个"extern"。这是什么意思呢?当时我还不清楚,因为很多书上并没有讲多文件的程序应该怎么写。不过现在当你看完这一节时我想你就应该明白了。

OCK aspectratio="t" v:ext="edit">OCK>


首先我们来看看多文件程序成为可执行程序的全过程:

1.5

我们可以发现,库文件(扩展名为LIB,其实是一种特殊的已经编译好的程序。系统函数的定义都是存在LIB内,以使你看不到它们的源代码)是在最后的连接一步加入程序的,各个文件也是在这一步才建立联系的。

extern的作用就是告诉编译器此变量会在其它程序文件中声明。把这种外部变量声明放在头文件里,再在每个文件中都包含这个头文件,然后只要在任何一个文件中声明变量,所有文件就都可以使用这个变量了。如果不加extern,各个文件使用的变量虽然同名但内容不会统一。

在各个文件中也需要先声明函数,之后才能使用它。不过这时用不着使用extern了。

最后我们来看看一个简单的多文件程序的例子:

/*---------------main.h----------------*/

#if !(defined MAIN_H)

#include <iostream>

using namespace std;

extern int a;

void print();

#define MAIN_H

#endif

/*---------------main.cpp----------------*/

#include "main.h"

int a;

void main()

{

a=3;

print();

}

/*---------------function.cpp----------------*/

#include "main.h"

void print()

{

cout<<a;

}

1.9 常用函数

C++与其它语言的一大区别是提供了庞大的函数库,能用好它就可以提高你的效率。

先看看<cstdlib>里面的:

int rand( ):返回一个随机的整数。

void srand(int):根据参数重新初始化随机数产生器。

int/float abs(int/float):返回数的绝对值。

min/max(a,b):返回a和b中的较小/大者,用#define定义的,大家不用担心效率。

int atoi(char *s);,返回由s字符串转换成的整数。

double atof(char *s);,返回由s字符串转换成的浮点数。

char* gcvt(double num, int sig, char *str);,num为待转换浮点数,sig为转换后数的有效数字数,str为目标字符串起点。函数返回指向str的指针。举个例子,如果sig=5那么9.876会转换成"9.876",-123.4578会变成"-123.46",6.7898e5就成了"6.7898e+05"。

然后是<cmath>里面的数学函数:

sin、cos、tan:这个你应该懂吧J。

asin、acos、atan:反三角函数。

sinh、cosh、tanh:双曲三角函数。

log、log10:自然和常用对数。

exp、pow10:上面两个函数的反函数。

pow(x,y):返回x的y次幂。

sqrt:开平方根。

ceil:返回最小的不小于x的整数。

floor:返回最大的不大于x的整数。

hypot(x,y):返回x的平方加上y的平方再开方的值。

文件读写函数在<cstdio>里面,使用方法是:

首先定义指向文件的指针并打开文件,例如FILE *file = fopen ("aa.bbb", "rb");,其中aa.bbb为你要打开的文件名(注意,如果在VC.net的开发环境中按F5或Ctrl+F5执行程序,程序的默认文件读取目录是工程的目录,而不是工程目录下的Debug或是Release目录),如果有路径则要用"\\"或"/"代替"\"。"rb"是打开的模式,基本模式有这些:

 楼主| 发表于 2006-1-6 11:37:13 | 显示全部楼层

表1.1

可读数据 可写数据 打开文件时读写指针位置 如不存在则创立新文件
r 文件头
w >> 文件头
a >> 文件尾
r+ 文件头
w+ 文件头
a+ 文件尾


在基本模式后加上b或t可设定要打开的是二进制文件还是文本文件。对于前者,我们打开文件之后用fread可以读入数据,读写指针也会随之后移。fread的使用方法为:fread(p, size, n, file);,p为指向读出的数据将存放的位置的指针,size为每一个数据块的字节数,n为要读多少个数据块,file则为刚才定义的指向文件的指针。例如fread(&a[0][0], sizeof(a[0][0]), sizeof(a)/sizeof(a[0][0]), file);可将数据读入二维数组a。需要注意的是这时你得到的数据为ASCII码!例如如果文件的内容为"10",你将读出49和48这两个数(1和0的ASCII码)。用fwrite则可以写数据,形式与fread一模一样,使用方法也相同

对于文本文件,我们应该使用fscanf( )和fprintf( )函数来读取和写入数据。这两个函数比较灵活,让我们看下面一段程序:>>

#include <cstdio>>>

using namespace std;>>

char s[5]="abcd";

int i=967;

float f=3.1415;

char c='x';

void main()

{

FILE *file=fopen("aa.txt","wt+");

fprintf(file,"str1=%s",s); //%s表示在这个位置上是一个字符串

fprintf(file,"\nint2 = %d",i); //%d表示整数,\n表示换行

fprintf(file,"\nfloat3=\n%f\nCH AR 4 = %c",f,c);

//%f表示浮点数,%c表示字符,可以把几个fprintf合起来写

fclose(file);

}

运行完后aa.txt的内容是:

str1=abcd

int2 = 967

float3=

3.141500

CH AR 4 = x

fscanf( )和fprintf( )的使用方法几乎是完全一样的,唯一的区别是,如果你要把数据读入普通变量,要在变量的前面加一个"&",使fprintf可以修改变量的值。当然,如果要读入的是字符串之类的指针就不必这样了。

fseek可以移动读写指针,形式为fseek(file, offset, whence);,file为文件指针,whence为寻址开始地点,0代表开头,1代表当前位置,2代表文件尾。offset则为需移动的字节数。

使用ftell(file);可以得知当前的读写指针位置(离文件头有多少个字节的距离)。

其实还有一种简单很多的文件读写方法(也更符合C++标准):

#include <fstream>

using namespace std;

int a;

……

iofstream file;

file.open("abc.dat"); //使用file.open("abc.dat", ios::binary);可指定为二进制模式

file>>a; //就像cin

file<<"abcdefg"; //就像cout

……

接着要说的是常用的字符串函数,在<cstring>内有它们的定义。

char *strcpy(char *dest, char *src);,该函数使dest=src并返回新的dest。使用它还可以实现字符串和字符数组之间的转换。

char* strcat(char *dest, char *src);,将src连接到dest的后面,并返回新的dest。

char* strstr(char *s1, char *s2);,返回指向s2在s1中第一次出现的位置的指针。

char* strchr(char *s1, char c);,返回指向c在s1中第一次出现的位置的指针。

char* strlwr(char *s);,将s中的所有大写字母转为小写。

char* strset(char *s, char c);,将s内所有字符替换为字符c。

int strlen(char *s);,返回字符串的长度。

最后是<memory>中的内存函数:

memcpy(char *dest, char *src, int n);,将从src开始的n个字节的内存内容拷贝到从dest开始的内存中。注意dest和src在内存中的位置不能重叠。

memmove(char *dest, char *src, int n);,也可以实现拷贝,dest和src在内存中的位置可以重叠。当然,它比memcpy慢。

memset(s, c, n);,将从s开始的n个字节都设为c。可以用来将数组和结构清零。

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2025-6-23 21:24

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表