13b3ebbe14
把川大 wiki「02.Qt方向」课程代码整理为 CMake 管理的可编译运行项目。
Part1 C/C++ 强化 (p01/, 144 目标, host gcc/g++ 构建, 全绿):
- 修复全部编译失败:补 <cstring>/<fstream>/<iomanip>/<vector> 等缺失头
(common_fix + infer_stl_includes 自动检测注入)、修正 pause() 壳注入
(改检测已转换后的 pause 调用)、namespace 提升出函数体、const 成员函数
正确性、dedupe 不再破坏函数重载集、K&R 隐式 int / 动态异常规范等
C++17 移除特性的可编译等价改写。
- 目录采用深缩写 p01/ch04/s03/ (原 part1_cpp/ch04_class_object/03_ctor_dtor)。
Part3 Qt 桌面 (p03/, 29 目标, 隔离 Qt 5.14.2 构建, 全绿):
- gen_part3.py: Qt 外壳模板 (main+QApplication+show, QTimer 自动退出便于
offscreen 验证)、AUTOMOC + .moc include、rewrite_* 处理散文混入/重载/
资源依赖、生成占位 png/gif + .qrc + rundata 样例数据。
- 修正 QLable 笔误、全角分号、.→->、Windows 路径、QApplication 阻塞事件
循环改为 QCoreApplication 等。
任务1 概念/版本验证:
- tools/std_probe.py: 跨 -std= 编译探针 (--pedantic-errors 让已废除特性硬失败)。
- tools/demo_versions/: K&R 隐式 int、三目左值、void* 转换、enum 赋整、
const 经指针、动态异常规范 共 6 个跨版本演示。
- docs/VERSION_NOTES.md: 每条「原文说法→C17/C++17 是否成立→何版本成立→已验证」。
文档: 顶层 README、docs/SOURCE_PAGES.md (页面→目录映射)、
docs/ERRATA.md (Part1 修正)、docs/ERRATA_part3.md (Part3 修正)。
另修复 tools/run.sh / run_qt.sh 的 ${1:?...} 引号语法 bug。
隔离 Qt 验证: ldd 0 系统 Qt 引用; 173/173 目标全绿; p03 offscreen 运行通过。
1 line
58 KiB
JSON
1 line
58 KiB
JSON
{"id":"58954439","type":"page","status":"current","title":"03.C++对C的扩展","version":{"by":{"type":"known","username":"ethanliu","userKey":"2c90931b6cfb654f016d5c69becc0001","profilePicture":{"path":"/images/icons/profilepics/default.svg","width":48,"height":48,"isDefault":true},"displayName":"刘浩","_links":{"self":"https://wiki.suncaper.net/rest/api/user?key=2c90931b6cfb654f016d5c69becc0001"},"_expandable":{"status":""}},"when":"2022-07-09T01:51:24.000Z","message":"","number":1,"minorEdit":false,"hidden":false,"_links":{"self":"https://wiki.suncaper.net/rest/experimental/content/58954439/version/1"},"_expandable":{"content":"/rest/api/content/58954439"}},"body":{"storage":{"value":"<ac:layout><ac:layout-section ac:type=\"two_right_sidebar\"><ac:layout-cell><h2><span>3.1 ::</span>作用域运算符</h2><p>通常情况下,如果有两个同名变量,一个是全局变量,另一个是局部变量,那么局部变量在其作用域内具有较高的优先权,它将屏蔽全局变量。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"c8a68ffb-20cf-4bf9-8541-834403cdf1ff\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//全局变量\nint a = 10;\nvoid test(){\n\t//局部变量\n\tint a = 20;\n\t//全局a被隐藏\n\tcout << \"a:\" << a << endl;\n}]]></ac:plain-text-body></ac:structured-macro><p>程序的输出结果是<span>a:20</span>。在<span>test</span>函数的输出语句中,使用的变量<span>a</span>是<span>test</span>函数内定义的局部变量,因此输出的结果为局部变量<span>a</span>的值。</p><p>作用域运算符可以用来解决局部变量与全局变量的重名问题</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"88b8feab-99cd-49ea-b47a-77b572d7d837\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//全局变量\nint a = 10;\n//1. 局部变量和全局变量同名\nvoid test(){\n\tint a = 20;\n\t//打印局部变量a\n\tcout << \"局部变量a:\" << a << endl;\n\t//打印全局变量a\n\tcout << \"全局变量a:\" << ::a << endl;\n}]]></ac:plain-text-body></ac:structured-macro><p>这个例子可以看出,作用域运算符可以用来解决局部变量与全局变量的重名问题,即在局部变量的作用域内,可用<span>::</span>对被屏蔽的同名的全局变量进行访问。</p><h2><span>3.2 </span>名字控制</h2><p>创建名字是程序设计过程中一项最基本的活动,当一个项目很大时,它会不可避免地包含大量名字。<span>c++</span>允许我们对名字的产生和名字的可见性进行控制。</p><p>我们之前在学习c语言可以通过static关键字来使得名字只得在本编译单元内可见,在c++中我们将通过一种通过命名空间来控制对名字的访问。</p><h3><span>3.2.1 C++</span>命名空间<span>(namespace)</span></h3><p>在<span>c++</span>中,名称(<span>name</span>)可以是符号常量、变量、函数、结构、枚举、类和对象等等。工程越大,名称互相冲突性的可能性越大。另外使用多个厂商的类库时,也可能导致名称冲突。为了避免,在大规模程序的设计中,以及在程序员使用各种各样的<span>C++</span>库时,这些标识符的命名发生冲突,标准<span>C++</span>引入关键字<span>namespace</span>(命名空间<span>/</span>名字空间<span>/</span>名称空间),可以更好地控制标识符的作用域。</p><h3>3.2.2命名空间使用语法</h3><ul><li>创建一个命名空间<span>:</span></li></ul><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"f4e5deb8-13cb-4ebe-9def-4c614198ba28\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[namespace A{\n\tint a = 10;\n}\nnamespace B{\n\tint a = 20;\n}\nvoid test(){\n\tcout << \"A::a : \" << A::a << endl;\n\tcout << \"B::a : \" << B::a << endl;\n}]]></ac:plain-text-body></ac:structured-macro><ul><li>命名空间只能全局范围内定义(<span style=\"color: rgb(255,0,0);\"><strong>以下错误写法</strong></span>)</li></ul><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"98abd374-5637-45db-907c-e1403a41d173\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[void test(){\n\tnamespace A{\n\t\tint a = 10;\n\t}\n\tnamespace B{\n\t\tint a = 20;\n\t}\n\tcout << \"A::a : \" << A::a << endl;\n\tcout << \"B::a : \" << B::a << endl;\n}]]></ac:plain-text-body></ac:structured-macro><ul><li>命名空间可嵌套命名空间</li></ul><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"e9ea1f40-a5e8-40d7-b068-e4e5e8653cf3\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[namespace A{\n\tint a = 10;\n\tnamespace B{\n\t\tint a = 20;\n\t}\n}\nvoid test(){\n\tcout << \"A::a : \" << A::a << endl;\n\tcout << \"A::B::a : \" << A::B::a << endl;\n}]]></ac:plain-text-body></ac:structured-macro><ul><li>命名空间是开放的,即可以随时把新的成员加入已有的命名空间中</li></ul><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"e7916d6f-f3b9-42d4-af54-767f9c7a4290\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[namespace A{\n\tint a = 10;\n}\n\nnamespace A{\n\tvoid func(){\n\t\tcout << \"hello namespace!\" << endl;\n\t}\n}\n\nvoid test(){\n\tcout << \"A::a : \" << A::a << endl;\n\tA::func();\n}]]></ac:plain-text-body></ac:structured-macro><ul><li>声明和实现可分离</li></ul><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"f5fdf3be-e6e0-49ef-9f2a-0a5b987277ad\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[namespace MySpace{\n\tvoid func1();\n\tvoid func2(int param);\n}]]></ac:plain-text-body></ac:structured-macro><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"a4fae24b-cc97-4601-9109-963a1d356ac1\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[void MySpace::func1(){\n\tcout << \"MySpace::func1\" << endl;\n}\nvoid MySpace::func2(int param){\n\tcout << \"MySpace::func2 : \" << param << endl;\n}]]></ac:plain-text-body></ac:structured-macro><ul><li>无名命名空间,意味着命名空间中的标识符只能在本文件内访问,相当于给这个标识符加上了<span>static</span>,使得其可以作为内部连接</li></ul><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"cf4bb7d0-5957-4523-8369-71fe538f58e7\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[namespace{\n\tint a = 10;\n\tvoid func(){ cout << \"hello namespace\" << endl; }\n}\nvoid test(){\n\tcout << \"a : \" << a << endl;\n\tfunc();\n}]]></ac:plain-text-body></ac:structured-macro><p><br /></p><ul><li>命名空间别名</li></ul><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"2e0d6b73-ffab-4e83-9bc0-2e52015e565a\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[namespace veryLongName{\n\t\n\tint a = 10;\n\tvoid func(){ cout << \"hello namespace\" << endl; }\n}\n\nvoid test(){\n\tnamespace shortName = veryLongName;\n\tcout << \"veryLongName::a : \" << shortName::a << endl;\n\tveryLongName::func();\n\tshortName::func();\n}]]></ac:plain-text-body></ac:structured-macro><h3>3.2.3 using声明</h3><p>using声明可使得指定的标识符可用。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"39f09387-4a07-4ebe-8f05-7a37080d17a9\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[namespace A{\n\tint paramA = 20;\n\tint paramB = 30;\n\tvoid funcA(){ cout << \"hello funcA\" << endl; }\n\tvoid funcB(){ cout << \"hello funcA\" << endl; }\n}\n\nvoid test(){\n\t//1. 通过命名空间域运算符\n\tcout << A::paramA << endl;\n\tA::funcA();\n\t//2. using声明\n\tusing A::paramA;\n\tusing A::funcA;\n\tcout << paramA << endl;\n\t//cout << paramB << endl; //不可直接访问\n\tfuncA();\n\t//3. 同名冲突\n\t//int paramA = 20; //相同作用域注意同名冲突\n}]]></ac:plain-text-body></ac:structured-macro><p>using声明碰到函数重载</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"21415273-8f6b-4e93-86ab-59c275e97180\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[namespace A{\n\tvoid func(){}\n\tvoid func(int x){}\n\tint func(int x,int y){}\n}\nvoid test(){\n\tusing A::func;\n\tfunc();\n\tfunc(10);\n\tfunc(10, 20);\n}]]></ac:plain-text-body></ac:structured-macro><h3>3.2.4 using编译指令</h3><p>using编译指令使整个命名空间标识符可用<span>.</span></p><p><br /></p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"08aa89c2-2948-4fa8-b5d7-7824835d4794\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[namespace A{\n\tint paramA = 20;\n\tint paramB = 30;\n\tvoid funcA(){ cout << \"hello funcA\" << endl; }\n\tvoid funcB(){ cout << \"hello funcB\" << endl; }\n}\n\nvoid test01(){\n\tusing namespace A;\n\tcout << paramA << endl;\n\tcout << paramB << endl;\n\tfuncA();\n\tfuncB();\n\n\t//不会产生二义性\n\tint paramA = 30;\n\tcout << paramA << endl;\n}\n\nnamespace B{\n\tint paramA = 20;\n\tint paramB = 30;\n\tvoid funcA(){ cout << \"hello funcA\" << endl; }\n\tvoid funcB(){ cout << \"hello funcB\" << endl; }\n}\n\nvoid test02(){\n\tusing namespace A;\n\tusing namespace B;\n\t//二义性产生,不知道调用A还是B的paramA\n\t//cout << paramA << endl;\n}\n\n]]></ac:plain-text-body></ac:structured-macro><p><strong>注意:使用<span>using</span>声明或<span>using</span>编译指令会增加命名冲突的可能性。也就是说,如果有名称空间,并在代码中使用作用域解析运算符,则不会出现二义性。</strong></p><h3><span>3.2.5 </span>命名空间使用</h3><p>我们刚讲的一些东西一开始会觉得难一些,这些东西以后还是挺常用,只要理解了它们的工作机理,使用它们非常简单。</p><p>需要记住的关键问题是当引入一个全局的<span>using</span>编译指令时,就为该文件打开了该命名空间,它不会影响任何其他的文件,所以可以在每一个实现文件中调整对命名空间的控制。比如,如果发现某一个实现文件中有太多的<span>using</span>指令而产生的命名冲突,就要对该文件做个简单的改变,通过明确的限定或者<span>using</span>声明来消除名字冲突,这样不需要修改其他的实现文件。</p><h3>3.3 全局变量检测增强</h3><p>c语言代码:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"bf9b6fea-675f-4a12-b4be-34888df3b656\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[int a = 10; //赋值,当做定义\nint a; //没有赋值,当做声明\n\nint main(){\n\tprintf(\"a:%d\\n\",a);\n\treturn EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro><p>此代码在<span>c++</span>下编译失败<span>,</span>在<span>c</span>下编译通过<span>.</span></p><h2><span>3.4 C</span>++中所有的变量和函数都必须有类型</h2><p>c语言代码:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"0fc12320-3eed-4b2f-b8ed-232ea23d8804\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//i没有写类型,可以是任意类型\nint fun1(i){\n\tprintf(\"%d\\n\", i);\n\treturn 0;\n}\n//i没有写类型,可以是任意类型\nint fun2(i){\n\tprintf(\"%s\\n\", i);\n\treturn 0;\n}\n//没有写参数,代表可以传任何类型的实参\nint fun3(){ \n\tprintf(\"fun33333333333333333\\n\");\n\treturn 0;\n}\n\n//C语言,如果函数没有参数,建议写void,代表没有参数\nint fun4(void){\n\tprintf(\"fun4444444444444\\n\");\n\treturn 0;\n}\n\ng(){\n\treturn 10;\n}\n\nint main(){\n\n\tfun1(10);\n\tfun2(\"abc\");\n\tfun3(1, 2, \"abc\");\n\tprintf(\"g = %d\\n\", g());\n\n\treturn 0;\n}]]></ac:plain-text-body></ac:structured-macro><p>以上<span>c</span>代码<span>c</span>编译器编译可通过,<span>c++</span>编译器无法编译通过。</p><ul><li>在<span>C</span>语言中,<span>int fun() </span>表示返回值为<span>int</span>,接受任意参数的函数,<span>int fun(void) </span>表示返回值为<span>int</span>的无参函数。</li><li>在<span style=\"letter-spacing: 0.0px;\">C++ </span><span style=\"letter-spacing: 0.0px;\">中,</span><span style=\"letter-spacing: 0.0px;\">int fun() </span><span style=\"letter-spacing: 0.0px;\">和</span><span style=\"letter-spacing: 0.0px;\">int fun(void) </span><span style=\"letter-spacing: 0.0px;\">具有相同的意义,都表示返回值为</span><span style=\"letter-spacing: 0.0px;\">int</span><span style=\"letter-spacing: 0.0px;\">的无参函数。</span></li></ul><h2>3.5 更严格的类型转换</h2><p>在<span>C++</span>,不同类型的变量一般是不能直接赋值的,需要相应的强转。</p><p>c语言代码:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"69b4b66f-edde-4474-b870-ad3326801cc6\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[typedef enum COLOR{ GREEN, RED, YELLOW } color;\nint main(){\n\tcolor mycolor = GREEN;\n\tmycolor = 10;\n\tprintf(\"mycolor:%d\\n\", mycolor);\n\tchar* p = malloc(10);\n\treturn EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro><p>以上<span>c</span>代码<span>c</span>编译器编译可通过,<span>c++</span>编译器无法编译通过。</p><h2><span>3.6 struct</span>类型加强</h2><ul><li>c中定义结构体变量需要加上<span>struct</span>关键字,<span>c++</span>不需要。</li><li>c中的结构体只能定义成员变量,不能定义成员函数。<span>c++</span>即可以定义成员变量,也可以定义成员函数。</li></ul><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"8cf2a47b-f861-45a3-bb9c-3a13d7726920\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//1. 结构体中即可以定义成员变量,也可以定义成员函数\nstruct Student{\n\tstring mName;\n\tint mAge;\n\tvoid setName(string name){ mName = name; }\n\tvoid setAge(int age){ mAge = age; }\n\tvoid showStudent(){\n\t\tcout << \"Name:\" << mName << \" Age:\" << mAge << endl;\n\t}\n};\n\n//2. c++中定义结构体变量不需要加struct关键字\nvoid test01(){\n\tStudent student;\n\tstudent.setName(\"John\");\n\tstudent.setAge(20);\n\tstudent.showStudent();\n}]]></ac:plain-text-body></ac:structured-macro><h2><span>3.7 “</span>新增”<span>bool</span>类型关键字</h2><p>标准<span>c++</span>的<span>bool</span>类型有两种内建的常量<span>true(</span>转换为整数<span>1)</span>和<span>false(</span>转换为整数<span>0)</span>表示状态。这三个名字都是关键字。</p><ul><li>bool类型只有两个值,<span>true(1</span>值<span>)</span>,<span>false(0</span>值<span>)</span></li><li>bool类型占<span>1</span>个字节大小</li><li>给<span>bool</span>类型赋值时,非<span>0</span>值会自动转换为<span>true(1),0</span>值会自动转换<span>false(0)</span></li></ul><p><br /></p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"32cf8a89-94ff-4bd2-882c-df23f2bff1c8\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[void test()\n{\tcout << sizeof(false) << endl; //为1,//bool类型占一个字节大小\n\tbool flag = true; // c语言中没有这种类型\n\tflag = 100; //给bool类型赋值时,非0值会自动转换为true(1),0值会自动转换false(0)\n}]]></ac:plain-text-body></ac:structured-macro><p><strong>[c</strong><strong>语言中的<span>bool</span>类型<span>]</span></strong></p><p>c语言中也有<span>bool</span>类型,在<span>c99</span>标准之前是没有<span>bool</span>关键字,<span>c99</span>标准已经有<span>bool</span>类型,包含头文件<span>stdbool.h,</span>就可以使用和<span>c++</span>一样的<span>bool</span>类型。</p><p><br /></p><h2><span>3.8 </span>三目运算符功能增强</h2><ul><li>c语言三目运算表达式返回值为数据值,为右值,不能赋值。</li></ul><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"5c18c4d7-4391-43c3-8d34-e76665c6d5c6\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[\tint a = 10;\n\tint b = 20;\n\tprintf(\"ret:%d\\n\", a > b ? a : b);\n\t//思考一个问题,(a > b ? a : b) 三目运算表达式返回的是什么?\n\t\n\t//(a > b ? a : b) = 100;\n\t//返回的是右值]]></ac:plain-text-body></ac:structured-macro><ul><li>c++语言三目运算表达式返回值为变量本身<span>(</span>引用<span>)</span>,为左值,可以赋值。</li></ul><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"527ab483-3164-4861-b8d2-c321d04af66b\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[\tint a = 10;\n\tint b = 20;\n\tprintf(\"ret:%d\\n\", a > b ? a : b);\n\t//思考一个问题,(a > b ? a : b) 三目运算表达式返回的是什么?\n\n\tcout << \"b:\" << b << endl;\n\t//返回的是左值,变量的引用\n\t(a > b ? a : b) = 100;//返回的是左值,变量的引用\n\tcout << \"b:\" << b << endl;]]></ac:plain-text-body></ac:structured-macro><h2><span>3.9 C/C++</span>中的<span>const</span></h2><h3><span>3.9.1 const</span>概述</h3><p>const单词字面意思为常数,不变的。它是<span>c/c++</span>中的一个关键字,是一个限定符,它用来限定一个变量不允许改变,它将一个对象转换成一个常量。</p><p style=\"margin-left: 30.0px;\">const int a = 10;</p><p style=\"margin-left: 30.0px;\">a= 100; //编译错误<span>,const</span>是一个常量,不可修改</p><h3><span>3.9.2 C/C++</span>中<span>const</span>的区别</h3><h4><span>3.9.2.1 C</span>中的<span>const </span></h4><p>常量的引进是在<span>c++</span>早期版本中,当时标准<span>C</span>规范正在制定。那时,尽管<span>C</span>委员会决定在<span>C</span>中引入<span>const,</span>但是,他们<span>c</span>中的<span>const</span>理解为”一个不能改变的普通变量”,也就是认为<span>const</span>应该是一个只读变量,既然是变量那么就会给<span>const</span>分配内存,并且在<span>c</span>中<span>const</span>是一个全局只读变量,<span>c</span>语言中<span>const</span>修饰的只读变量是外部连接的。</p><p>如果这么写<span>:</span></p><table class=\"wrapped\"><colgroup><col /></colgroup><tbody><tr><td><p>const int arrSize <strong>=</strong> 10<strong>;</strong></p><p>int arr<strong>[</strong>arrSize<strong>];</strong></p></td></tr></tbody></table><p>看似是一件合理的编码,但是这将得出一个错误。 因为<span>arrSize</span>占用某块内存,所以<span>C</span>编译器不知道它在编译时的值是多少?</p><h4><span>3.8.2.1 C++</span>中的<span>const </span></h4><p><strong>在<span>c++</span>中,一个<span>const</span>不必创建内存空间,而在<span>c</span>中,一个<span>const</span>总是需要一块内存空间。</strong>在<span>c++</span>中,是否为<span>const</span>常量分配内存空间依赖于如何使用。一般说来,如果一个<span>const</span>仅仅用来把一个名字用一个值代替<span>(</span>就像使用<span>#define</span>一样<span>)</span>,那么该存储局空间就不必创建。</p><p>如果存储空间没有分配内存的话,在进行完数据类型检查后,为了代码更加有效,值也许会折叠到代码中。</p><p>不过,取一个<span>const</span>地址<span>, </span>或者把它定义为<span>extern,</span>则会为该<span>const</span>创建内存空间。</p><p>在<span>c++</span>中,出现在所有函数之外的<span>const</span>作用于整个文件<span>(</span>也就是说它在该文件外不可见<span>)</span>,默认为内部连接,<span>c++</span>中其他的标识符一般默认为外部连接。</p><h4><span>3.8.2.3 C/C++</span>中<span>const</span>异同总结</h4><ul><li>c语言全局<span>const</span>会被存储到只读数据段。<span>c++</span>中全局<span>const</span>当声明<span>extern</span>或者对变量取地址时,编译器会分配存储地址,变量存储在只读数据段。两个都受到了只读数据段的保护,不可修改。</li></ul><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"5dac5751-7d20-4721-90bf-da0fb9be67b7\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[ const int constA = 10;\n int main(){\n int* p = (int*)&constA;\n *p = 200;\n }]]></ac:plain-text-body></ac:structured-macro><p><span style=\"letter-spacing: 0.0px;\"><br />c语言中局部</span><span style=\"letter-spacing: 0.0px;\">const</span><span style=\"letter-spacing: 0.0px;\">存储在堆栈区,只是不能通过变量直接修改</span><span style=\"letter-spacing: 0.0px;\">const</span><span style=\"letter-spacing: 0.0px;\">只读变量的值,但是可以跳过编译器的检查,通过指针间接修改</span><span style=\"letter-spacing: 0.0px;\">const</span><span style=\"letter-spacing: 0.0px;\">值。</span>以上代码在<span>c/c++</span>中编译通过,在运行期,修改<span>constA</span>的值时,发生写入错误。原因是修改只读数据段的数据。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"17362751-c957-4504-bc44-d3f758877123\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[ const int constA = 10;\n\n int* p = (int*)&constA;\n\n *p = 300;\n\n printf(\"constA:%d\\n\",constA);\n\n printf(\"*p:%d\\n\", *p);]]></ac:plain-text-body></ac:structured-macro><p><span style=\"letter-spacing: 0.0px;\">运行结果:constA: 300 </span></p><p><span style=\"letter-spacing: 0.0px;\"> *p:300</span></p><p>c语言中,通过指针间接赋值修改了<span>constA</span>的值。</p><p>c++中对于局部的<span>const</span>变量要区别对待:</p><p style=\"margin-left: 30.0px;\">1.对于基础数据类型,也就是<span>const int a = 10</span>这种,编译器会把它放到符号表中,不分配内存,当对其取地址时,会分配内存。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"57ae8be2-0195-4845-8617-3db9c50bff23\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[\tconst int constA = 10;\n\tint* p = (int*)&constA;\n\t*p = 300;\n\tcout << \"constA:\" << constA << endl;\n\tcout << \"*p:\" << *p << endl;]]></ac:plain-text-body></ac:structured-macro><p><br /></p><p><span style=\"letter-spacing: 0.0px;\">运行结果:constA: 10<br /> *p:300</span></p><p>constA在符号表中,当我们对<span>constA</span>取地址,这个时候为<span>constA</span>分配了新的空间,<span>*p</span>操作的是分配的空间,而<span>constA</span>是从符号表获得的值。</p><p style=\"margin-left: 30.0px;\">2.对于基础数据类型,如果用一个变量初始化<span>const</span>变量,如果<span>const int a = b,</span>那么也是会给<span>a</span>分配内存。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"8b270c2f-87ab-4db0-8c1a-09450722c304\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[\tint b = 10;\n\tconst int constA = b;\n\tint* p = (int*)&constA;\n\t*p = 300;\n\tcout << \"constA:\" << constA << endl;\n\tcout << \"*p:\" << *p << endl;]]></ac:plain-text-body></ac:structured-macro>运行结果:constA: 300<p><span style=\"letter-spacing: 0.0px;\"> *p:300</span></p><p>constA 分配了内存,所以我们可以修改<span>constA</span>内存中的值。</p><p style=\"margin-left: 30.0px;\">3.对于自定数据类型,比如类对象,那么也会分配内存。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"55f1f358-dede-4fb8-b928-da77abf2a4df\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[ const Person person; //未初始化age\n\t//person.age = 50; //不可修改\n\tPerson* pPerson = (Person*)&person;\n\t//指针间接修改\n\tpPerson->age = 100;\n\tcout << \"pPerson->age:\" << pPerson->age << endl;\n\tpPerson->age = 200;\n\tcout << \"pPerson->age:\" << pPerson->age << endl;]]></ac:plain-text-body></ac:structured-macro><p>运行结果:pPerson→age:100</p><p><span> pPerson->age:200</span></p><p>为<span>person</span>分配了内存,所以我们可以通过指针的间接赋值修改<span>person</span>对象。</p><ul><li>c中<span>const</span>默认为外部连接,<span>c++</span>中<span>const</span>默认为内部连接<span>.</span>当<span>c</span>语言两个文件中都有<span>const int a</span>的时候,编译器会报重定义的错误。而在<span>c++</span>中,则不会,因为<span>c++</span>中的<span>const</span>默认是内部连接的。如果想让<span>c++</span>中的<span>const</span>具有外部连接,必须显示声明为<span>: extern const int a = 10;</span></li></ul><p>const由<span>c++</span>采用,并加进标准<span>c</span>中,尽管他们很不一样。在<span>c</span>中,编译器对待<span>const</span>如同对待变量一样,只不过带有一个特殊的标记,意思是<span>”</span>你不能改变我<span>”</span>。在<span>c++</span>中定义<span>const</span>时,编译器为它创建空间,所以如果在两个不同文件定义多个同名的<span>const</span>,链接器将发生链接错误。简而言之<span>,const</span>在<span>c++</span>中用的更好。</p><h3><span>3.9.3 </span>尽量以<span>const</span>替换<span>#define</span></h3><p>在旧版本<span>C</span>中,如果想建立一个常量,必须使用预处理器”</p><p>#define MAX 1024;</p><p>我们定义的宏<span>MAX</span>从未被编译器看到过,因为在预处理阶段,所有的<span>MAX</span>已经被替换为了<span>1024</span>,于是<span>MAX</span>并没有将其加入到符号表中。但我们使用这个常量获得一个编译错误信息时,可能会带来一些困惑,因为这个信息可能会提到<span>1024</span>,但是并没有提到<span>MAX.</span>如果<span>MAX</span>被定义在一个不是你写的头文件中,你可能并不知道<span>1024</span>代表什么,也许解决这个问题要花费很长时间。</p><p>解决办法就是用一个常量替换上面的宏。</p><p>const int max= 1024;</p><p><span> </span></p><p>const和<span>#define</span>区别总结<span>:</span></p><table class=\"wrapped\"><colgroup><col /></colgroup><tbody><tr><td><p>1.const有类型,可进行编译器类型安全检查。<span>#define</span>无类型,不可进行类型检查<span>.</span></p><p>2.const有作用域,而<span>#define</span>不重视作用域,默认定义处到文件结尾<span>.</span>如果定义在指定作用域下有效的常量,那么<span>#define</span>就不能用。</p></td></tr></tbody></table><p><span> </span></p><ol><li>宏常量没有类型,所以调用了<span>int</span>类型重载的函数。<span>const</span>有类型,所以调用希望的<span>short</span>类型函数?</li></ol><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"155dd2cc-5196-4810-ab48-aa4a0342018c\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[#define PARAM 128\n\nconst short param = 128;\n\nvoid func(short a){\n cout << \"short!\" << endl;\n}\nvoid func(int a){\n cout << \"int\" << endl;\n}]]></ac:plain-text-body></ac:structured-macro><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"2ff96484-ae61-41ea-9f43-32b5983eab53\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[void func1(){\n const int a = 10;\n #define A 20\n //#undef A //卸载宏常量A\n}\n\nvoid func2(){\n //cout << \"a:\" << a << endl; //不可访问,超出了const int a作用域\n cout << \"A:\" << A << endl; //#define作用域从定义到文件结束或者到#undef,可访问\n}\n\nint main(){\n func2();\n return EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro><p style=\"margin-left: 30.0px;\"><span style=\"letter-spacing: 0.0px;\"><br /> </span><span>2.</span><span style=\"letter-spacing: 0.0px;\">宏常量不重视作用域</span><span style=\"letter-spacing: 0.0px;\">.</span></p><p><strong>问题<span>: </span></strong>宏常量可以有命名空间吗?</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"1bcfa4b6-2db5-4f9f-8df1-58a8fda859b5\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[namespace MySpace{\n #define num 1024\n}\n\nvoid test(){\n //cout << MySpace::NUM << endl; //错误\n //int num = 100; //命名冲突\n cout << num << endl;\n}]]></ac:plain-text-body></ac:structured-macro><h2><span>3.10 </span>引用<span>(reference)</span></h2><h3><span>3.10.1 </span>引用基本用法</h3><p><strong>引用是<span>c++</span>对<span>c</span>的重要扩充。</strong>在<span>c/c++</span>中指针的作用基本都是一样的,但是<span>c++</span>增加了另外一种给函数传递地址的途径,这就是按引用传递<span>(pass-by-reference)</span>,它也存在于其他一些编程语言中,并不是<span>c++</span>的发明。</p><p><span> </span></p><ul><li>变量名实质上是一段连续内存空间的别名,是一个标号<span>(</span>门牌号<span>)</span></li><li>程序中通过变量来申请并命名内存空间</li><li>通过变量的名字可以使用存储空间</li></ul><p><span> </span></p><table class=\"wrapped\"><colgroup><col /></colgroup><tbody><tr><td><p><strong>对一段连续的内存空间只能取一个别名吗?</strong></p><p>c++中新增了引用的概念,引用可以作为一个已定义变量的别名。</p></td></tr></tbody></table><p>基本语法<span>: </span></p><table class=\"wrapped\"><tbody><tr><td><p>Type& ref = val;</p></td></tr></tbody></table><p>注意事项:</p><ul><li>&在此不是求地址运算,而是起标识作用。</li><li>类型标识符是指目标变量的类型</li><li>必须在声明引用变量时进行初始化。</li><li>引用初始化之后不能改变。</li><li>不能有<span>NULL</span>引用。必须确保引用是和一块合法的存储单元关联。</li><li>可以建立对数组的引用。</li></ul><p><br /></p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"9e79c556-6671-4091-b465-f9f84b37ca20\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//1. 认识引用\nvoid test01(){\n\n\tint a = 10;\n\t//给变量a取一个别名b\n\tint& b = a;\n\tcout << \"a:\" << a << endl;\n\tcout << \"b:\" << b << endl;\n\tcout << \"------------\" << endl;\n\t//操作b就相当于操作a本身\n\tb = 100;\n\tcout << \"a:\" << a << endl;\n\tcout << \"b:\" << b << endl;\n\tcout << \"------------\" << endl;\n\t//一个变量可以有n个别名\n\tint& c = a;\n\tc = 200;\n\tcout << \"a:\" << a << endl;\n\tcout << \"b:\" << b << endl;\n\tcout << \"c:\" << c << endl;\n\tcout << \"------------\" << endl;\n\t//a,b,c的地址都是相同的\n\tcout << \"a:\" << &a << endl;\n\tcout << \"b:\" << &b << endl;\n\tcout << \"c:\" << &c << endl;\n}\n//2. 使用引用注意事项\nvoid test02(){\n\t//1) 引用必须初始化\n\t//int& ref; //报错:必须初始化引用\n\t//2) 引用一旦初始化,不能改变引用\n\tint a = 10;\n\tint b = 20;\n\tint& ref = a;\n\tref = b; //不能改变引用\n\t//3) 不能对数组建立引用\n\tint arr[10];\n\t//int& ref3[10] = arr;\n}]]></ac:plain-text-body></ac:structured-macro><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"9d4844f2-6483-45df-b830-e2e68695bfec\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[\t//1. 建立数组引用方法一\n\ttypedef int ArrRef[10];\n\tint arr[10];\n\tArrRef& aRef = arr;\n\tfor (int i = 0; i < 10;i ++){\n\t\taRef[i] = i+1;\n\t}\n\tfor (int i = 0; i < 10;i++){\n\t\tcout << arr[i] << \" \";\n\t}\n\tcout << endl;\n\t//2. 建立数组引用方法二\n\tint(&f)[10] = arr;\n\tfor (int i = 0; i < 10; i++){\n\t\tf[i] = i+10;\n\t}\n\tfor (int i = 0; i < 10; i++){\n\t\tcout << arr[i] << \" \";\n\t}\n\tcout << endl;]]></ac:plain-text-body></ac:structured-macro><h3><span>3.10.2 </span>函数中的引用</h3><p>最常见看见引用的地方是在函数参数和返回值中。当引用被用作函数参数的时,在函数内对任何引用的修改,将对还函数外的参数产生改变。当然,可以通过传递一个指针来做相同的事情,但引用具有更清晰的语法。</p><p>如果从函数中返回一个引用,必须像从函数中返回一个指针一样对待。当函数返回值时,引用关联的内存一定要存在。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"f7314373-c477-48a3-91a7-165da8cca317\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//值传递\nvoid ValueSwap(int m,int n){\n\tint temp = m;\n\tm = n;\n\tn = temp;\n}\n//地址传递\nvoid PointerSwap(int* m,int* n){\n\tint temp = *m;\n\t*m = *n;\n\t*n = temp;\n}\n//引用传递\nvoid ReferenceSwap(int& m,int& n){\n\tint temp = m;\n\tm = n;\n\tn = temp;\n}\nvoid test(){\n\tint a = 10;\n\tint b = 20;\n\t//值传递\n\tValueSwap(a, b);\n\tcout << \"a:\" << a << \" b:\" << b << endl;\n\t//地址传递\n\tPointerSwap(&a, &b);\n\tcout << \"a:\" << a << \" b:\" << b << endl;\n\t//引用传递\n\tReferenceSwap(a, b);\n\tcout << \"a:\" << a << \" b:\" << b << endl;\n}]]></ac:plain-text-body></ac:structured-macro><p>通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单:<span> </span></p><ul><li>函数调用时传递的实参不必加“<span>&</span>”符</li><li>在被调函数中不必在参数前加“<span>*</span>”符</li></ul><p>引用作为其它变量的别名而存在,因此在一些场合可以代替指针。<span>C++</span>主张用引用传递取代地址传递的方式,因为引用语法容易且不易出错。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"3fc3bb36-8e2b-4335-bc21-4cc97332e2bc\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//返回局部变量引用\nint& TestFun01(){\n\tint a = 10; //局部变量\n\treturn a;\n}\n//返回静态变量引用\nint& TestFunc02(){\t\n\tstatic int a = 20;\n\tcout << \"static int a : \" << a << endl;\n\treturn a;\n}\nint main(){\n\t//不能返回局部变量的引用\n\tint& ret01 = TestFun01();\n\t//如果函数做左值,那么必须返回引用\n\tTestFunc02();\n\tTestFunc02() = 100;\n\tTestFunc02();\n\n\treturn EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro><ul><li>不能返回局部变量的引用。</li><li>函数当左值,必须返回引用。</li></ul><h3><span>3.10.3 </span>引用的本质</h3><p><strong>引用的本质在<span>c++</span>内部实现是一个指针常量<span>.</span></strong></p><table class=\"wrapped\"><colgroup><col /></colgroup><tbody><tr><td><p>Type<strong>&</strong> ref <strong>=</strong> val<strong>;</strong> // Type* const ref = &val;</p></td></tr></tbody></table><p><span> </span></p><p>c++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同,只是这个过程是编译器内部实现,用户不可见。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"dfea1cce-6102-4be3-ac5d-bfe70a956866\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//发现是引用,转换为 int* const ref = &a;\nvoid testFunc(int& ref){\n\tref = 100; // ref是引用,转换为*ref = 100\n}\nint main(){\n\tint a = 10;\n\tint& aRef = a; //自动转换为 int* const aRef = &a;这也能说明引用为什么必须初始化\n\taRef = 20; //内部发现aRef是引用,自动帮我们转换为: *aRef = 20;\n\tcout << \"a:\" << a << endl;\n\tcout << \"aRef:\" << aRef << endl;\n\ttestFunc(a);\n\treturn EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro><h3><span>3.10.4 </span>指针引用</h3><p>在<span>c</span>语言中如果想改变一个指针的指向而不是它所指向的内容,函数声明可能这样<span>:</span></p><p><br /></p><table class=\"wrapped\"><tbody><tr><td><p>void fun<strong>(</strong>int<strong>**);</strong></p></td></tr></tbody></table><p>给指针变量取一个别名。</p><table class=\"wrapped\"><tbody><tr><td><p>Type<strong>*</strong> pointer <strong>=</strong> <strong>NULL</strong><strong>;</strong> </p><p>Type<strong>*&</strong> <strong>=</strong> pointer<strong>;</strong></p></td></tr></tbody></table><p>Type* pointer = NULL; Type*& = pointer;</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"1ae227a2-f2df-4aa2-83fb-41dbca7e6ede\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[struct Teacher{\n\tint mAge;\n};\n//指针间接修改teacher的年龄\nvoid AllocateAndInitByPointer(Teacher** teacher){\n\t*teacher = (Teacher*)malloc(sizeof(Teacher));\n\t(*teacher)->mAge = 200; \n}\n//引用修改teacher年龄\nvoid AllocateAndInitByReference(Teacher*& teacher){\n\tteacher->mAge = 300;\n}\nvoid test(){\n\t//创建Teacher\n\tTeacher* teacher = NULL;\n\t//指针间接赋值\n\tAllocateAndInitByPointer(&teacher);\n\tcout << \"AllocateAndInitByPointer:\" << teacher->mAge << endl;\n\t//引用赋值,将teacher本身传到ChangeAgeByReference函数中\n\tAllocateAndInitByReference(teacher);\n\tcout << \"AllocateAndInitByReference:\" << teacher->mAge << endl;\n\tfree(teacher);\n}]]></ac:plain-text-body></ac:structured-macro><p>对于<span>c++</span>中的定义那个,语法清晰多了。函数参数变成指针的引用,用不着取得指针的地址。</p><h3><span>3.10.5 </span>常量引用</h3><p>常量引用的定义格式<span>:</span></p><table class=\"wrapped\"><tbody><tr><td><p>const Type<strong>&</strong> ref <strong>=</strong> val<strong>;</strong></p></td></tr></tbody></table><p><span> </span></p><p>常量引用注意:</p><ul><li>字面量不能赋给引用,但是可以赋给<span>const</span>引用</li><li>const修饰的引用,不能修改。</li></ul><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"ad91cd14-69c0-4414-99a2-1312bd6762a2\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[void test01(){\n\tint a = 100;\n\tconst int& aRef = a; //此时aRef就是a\n\t//aRef = 200; 不能通过aRef的值\n\ta = 100; //OK\n\tcout << \"a:\" << a << endl;\n\tcout << \"aRef:\" << aRef << endl;\n}\nvoid test02(){\n\t//不能把一个字面量赋给引用\n\t//int& ref = 100;\n\t//但是可以把一个字面量赋给常引用\n\tconst int& ref = 100; //int temp = 200; const int& ret = temp;\n}]]></ac:plain-text-body></ac:structured-macro><table class=\"wrapped\"><colgroup><col /></colgroup><tbody><tr><td><p> <strong>[const</strong><strong>引用使用场景<span>]</span></strong></p><p> 常量引用主要用在函数的形参,尤其是类的拷贝<span>/</span>复制构造函数。</p><p>将函数的形参定义为常量引用的好处<span>:</span></p><ul><li> 引用不产生新的变量,减少形参与实参传递时的开销。</li><li>由于引用可能导致实参随形参改变而改变,将其定义为常量引用可以消除这种副作用。</li></ul><p> 如果希望实参随着形参的改变而改变,那么使用一般的引用,如果不希望实参随着形参改变,那么使用常引用。</p></td></tr></tbody></table><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"1401c528-f385-438f-a55f-fa5480fe5c5e\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//const int& param防止函数中意外修改数据\nvoid ShowVal(const int& param){\n\tcout << \"param:\" << param << endl;\n}]]></ac:plain-text-body></ac:structured-macro><h2><span>3.11 </span>内联函数<span>(inline function)</span></h2><h3><span>3.11.1 </span>内联函数的引出</h3><p>c++从<span>c</span>中继承的一个重要特征就是效率。假如<span>c++</span>的效率明显低于<span>c</span>的效率,那么就会有很大的一批程序员不去使用<span>c++</span>了。</p><p>在<span>c</span>中我们经常把一些短并且执行频繁的计算写成宏,而不是函数,这样做的理由是为了执行效率,宏可以避免函数调用的开销,这些都由预处理来完成。</p><p>但是在<span>c++</span>出现之后,使用预处理宏会出现两个问题:</p><ul><li>第一个在<span>c</span>中也会出现,宏看起来像一个函数调用,但是会有隐藏一些难以发现的错误。</li><li>第二个问题是<span>c++</span>特有的,预处理器不允许访问类的成员,也就是说预处理器宏不能用作类类的成员函数。</li></ul><p><span> </span>为了保持预处理宏的效率又增加安全性,而且还能像一般成员函数那样可以在类里访问自如,<span>c++</span>引入了内联函数<span>(inline function).</span></p><p>内联函数为了继承宏函数的效率,没有函数调用时开销,然后又可以像普通函数那样,可以进行参数,返回值类型的安全检查,又可以作为成员函数。</p><h3><span>3.11.2 </span>预处理宏的缺陷</h3><p>预处理器宏存在问题的关键是我们可能认为预处理器的行为和编译器的行为是一样的。当然也是由于宏函数调用和函数调用在外表看起来是一样的,因为也容易被混淆。但是其中也会有一些微妙的问题出现<span>:</span></p><p>问题一:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"9a8d5619-a6a4-435b-9c55-8a992353ca19\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[#define ADD(x,y) x+y\ninline int Add(int x,int y){\n\treturn x + y;\n}\nvoid test(){\n\tint ret1 = ADD(10, 20) * 10; //希望的结果是300\n\tint ret2 = Add(10, 20) * 10; //希望结果也是300\n\tcout << \"ret1:\" << ret1 << endl; //210\n\tcout << \"ret2:\" << ret2 << endl; //300\n}]]></ac:plain-text-body></ac:structured-macro><p>问题二:</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"562ffe6b-c8c6-4d24-9cad-f1cfcebc2fa1\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[#define COMPARE(x,y) ((x) < (y) ? (x) : (y))\nint Compare(int x,int y){\n\treturn x < y ? x : y;\n}\nvoid test02(){\n\tint a = 1;\n\tint b = 3;\n\t//cout << \"COMPARE(++a, b):\" << COMPARE(++a, b) << endl; // 3\n\tcout << \"Compare(int x,int y):\" << Compare(++a, b) << endl; //2\n}]]></ac:plain-text-body></ac:structured-macro><p>问题三<span>:</span></p><p>预定义宏函数没有作用域概念,无法作为一个类的成员函数,也就是说预定义宏没有办法表示类的范围。</p><h3><span>3.11.3 </span>内联函数</h3><h4><span>3.11.3.1 </span>内联函数基本概念</h4><p>在<span>c++</span>中,预定义宏的概念是用内联函数来实现的,而<strong>内联函数本身也是一个真正的函数</strong>。内联函数具有普通函数的所有行为。唯一不同之处在于内联函数会在适当的地方像预定义宏一样展开,所以不需要函数调用的开销。因此应该不使用宏,使用内联函数。</p><ul><li>在普通函数<span>(</span>非成员函数<span>)</span>函数前面加上<span>inline</span>关键字使之成为内联函数。但是必须注意必须函数体和声明结合在一起,否则编译器将它作为普通函数来对待。</li></ul><table class=\"wrapped\"><tbody><tr><td><p>inline void func<strong>(</strong>int a<strong>);</strong></p></td></tr></tbody></table><p>以上写法没有任何效果,仅仅是声明函数,应该如下方式来做<span>:</span></p><table class=\"wrapped\"><colgroup><col /></colgroup><tbody><tr><td><p>inline int func<strong>(</strong>int a<strong>){</strong><strong>return</strong> <strong>++;}</strong></p></td></tr></tbody></table><p>注意<span>: </span>编译器将会检查函数参数列表使用是否正确,并返回值<span>(</span>进行必要的转换<span>)</span>。这些事预处理器无法完成的。</p><p>内联函数的确占用空间,但是内联函数相对于普通函数的优势只是省去了函数调用时候的压栈,跳转,返回的开销。我们可以理解为内联函数是以<strong>空间换时间</strong>。</p><h4><span>3.11.3.2 </span>类内部的内联函数</h4><p>为了定义内联函数,通常必须在函数定义前面放一个<span>inline</span>关键字。但是在类内部定义内联函数时并不是必须的。任何在类内部定义的函数自动成为内联函数。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"fecdd9e5-7bd7-456c-ba84-ef7e7915a911\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[class Person{\npublic:\n\tPerson(){ cout << \"构造函数!\" << endl; }\n\tvoid PrintPerson(){ cout << \"输出Person!\" << endl; }\n}]]></ac:plain-text-body></ac:structured-macro><p>构造函数<span>Person</span>,成员函数<span>PrintPerson</span>在类的内部定义,自动成为内联函数。</p><h4><span>3.11.3.3 </span>内联函数和编译器</h4><p>内联函数并不是何时何地都有效,为了理解内联函数何时有效,应该要知道编译器碰到内联函数会怎么处理?</p><p>对于任何类型的函数,编译器会将函数类型<span>(</span>包括函数名字,参数类型,返回值类型<span>)</span>放入到符号表中。同样,当编译器看到内联函数,并且对内联函数体进行分析没有发现错误时,也会将内联函数放入符号表。</p><p>当调用一个内联函数的时候,编译器首先确保传入参数类型是正确匹配的,或者如果类型不正完全匹配,但是可以将其转换为正确类型,并且返回值在目标表达式里匹配正确类型,或者可以转换为目标类型,内联函数就会直接替换函数调用,这就消除了函数调用的开销。假如内联函数是成员函数,对象<span>this</span>指针也会被放入合适位置。</p><p>类型检查和类型转换、包括在合适位置放入对象<span>this</span>指针这些都是预处理器不能完成的。</p><p><span> </span></p><p>但是<span>c++</span>内联编译会有一些限制,以下情况编译器可能考虑不会将函数进行内联编译:</p><table class=\"wrapped\"><colgroup><col /></colgroup><tbody><tr><td><ul><li>不能存在任何形式的循环语句</li><li>不能存在过多的条件判断语句</li><li>函数体不能过于庞大</li><li>不能对函数进行取址操作</li></ul></td></tr></tbody></table><p><br /></p><p><strong>内联仅仅只是给编译器一个建议,编译器不一定会接受这种建议,如果你没有将函数声明为内联函数,那么编译器也可能将此函数做内联编译。一个好的编译器将会内联小的、简单的函数。</strong></p><h2><span>3.12 </span>函数的默认参数</h2><p>c++在声明函数原型的时可为一个或者多个参数指定默认<span>(</span>缺省<span>)</span>的参数值,当函数调用的时候如果没有指定这个值,编译器会自动用默认值代替。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"8e51f8ab-dbfa-4ccb-a9a2-468b6f1ed302\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[void TestFunc01(int a = 10, int b = 20){\n\tcout << \"a + b = \" << a + b << endl;\n}\n//注意点:\n//1. 形参b设置默认参数值,那么后面位置的形参c也需要设置默认参数\nvoid TestFunc02(int a,int b = 10,int c = 10){}\n//2. 如果函数声明和函数定义分开,函数声明设置了默认参数,函数定义不能再设置默认参数\nvoid TestFunc03(int a = 0,int b = 0);\nvoid TestFunc03(int a, int b){}\n\nint main(){\n\t//1.如果没有传参数,那么使用默认参数\n\tTestFunc01();\n\t//2. 如果传一个参数,那么第二个参数使用默认参数\n\tTestFunc01(100);\n\t//3. 如果传入两个参数,那么两个参数都使用我们传入的参数\n\tTestFunc01(100, 200);\n\n\treturn EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro><p><strong>注意点:</strong></p><table class=\"wrapped\"><colgroup><col /></colgroup><tbody><tr><td><ul><li>函数的默认参数从左向右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须设置默认参数。</li><li>如果函数声明和函数定义分开写,函数声明和函数定义不能同时设置默认参数。</li></ul></td></tr></tbody></table><h2><span>3.13 </span>函数的占位参数</h2><p>c++在声明函数时,可以设置占位参数。占位参数只有参数类型声明,而没有参数名声明。一般情况下,在函数体内部无法使用占位参数。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"5c2e80b8-5cac-4f91-a7c8-9643a949fbca\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[void TestFunc01(int a,int b,int){\n\t//函数内部无法使用占位参数\n\tcout << \"a + b = \" << a + b << endl;\n}\n//占位参数也可以设置默认值\nvoid TestFunc02(int a, int b, int = 20){\n\t//函数内部依旧无法使用占位参数\n\tcout << \"a + b = \" << a + b << endl;\n}\nint main(){\n\n\t//错误调用,占位参数也是参数,必须传参数\n\t//TestFunc01(10,20); \n\t//正确调用\n\tTestFunc01(10,20,30);\n\t//正确调用\n\tTestFunc02(10,20);\n\t//正确调用\n\tTestFunc02(10, 20, 30);\n\n\treturn EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro><h2><span>3.14 </span>函数重载<span>(overload)</span></h2><h3><span>3.14.1 </span>函数重载概述</h3><table class=\"wrapped\"><colgroup><col /></colgroup><tbody><tr><td><p><span> </span><strong> </strong><strong>能使名字方便使用,是任何程序设计语言的一个重要特征!</strong></p></td></tr></tbody></table><p><span> </span></p><p>我们现实生活中经常会碰到一些字在不同的场景下具有不同的意思,比如汉语中的多音字“重”。</p><p>当我们说<span>: “</span>他好重啊,我都背不动<span>!”</span>我们根据上下文意思,知道“重”在此时此地表示重量的意思。</p><p>如果我们说“你怎么写了那么多重复的代码<span>? </span>维护性太差了<span>!</span>”这个地方我们知道,“重”表示重复的意思。</p><p>同样一个字在不同的场景下具有不同的含义。那么在<span>c++</span>中也有一种类似的现象出现,同一个函数名在不同场景下可以具有不同的含义。</p><p>在传统<span>c</span>语言中,函数名必须是唯一的,程序中不允许出现同名的函数。在<span>c++</span>中是允许出现同名的函数,这种现象称为函数重载。</p><p>函数重载的目的就是为了方便的使用函数名。</p><p>函数重载并不复杂,等大家学完就会明白什么时候需要用到他们,以及是如何编译,链接的。</p><h3><span>3.14.2 </span>函数重载</h3><h4><span>3.14.2.1 </span>函数重载基本语法</h4><p><strong>实现函数重载的条件:</strong></p><table class=\"wrapped\"><colgroup><col /></colgroup><tbody><tr><td><ul><li>同一个作用域</li><li>参数个数不同</li><li>参数类型不同</li><li>参数顺序不同</li></ul></td></tr></tbody></table><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"58af0db5-d420-4421-87b7-bcc53ddcdce3\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//1. 函数重载条件\nnamespace A{\n\tvoid MyFunc(){ cout << \"无参数!\" << endl; }\n\tvoid MyFunc(int a){ cout << \"a: \" << a << endl; }\n\tvoid MyFunc(string b){ cout << \"b: \" << b << endl; }\n\tvoid MyFunc(int a, string b){ cout << \"a: \" << a << \" b:\" << b << endl;}\n void MyFunc(string b, int a){cout << \"a: \" << a << \" b:\" << b << endl;}\n}\n//2.返回值不作为函数重载依据\nnamespace B{\n\tvoid MyFunc(string b, int a){}\n\t//int MyFunc(string b, int a){} //无法重载仅按返回值区分的函数\n}]]></ac:plain-text-body></ac:structured-macro><p><strong>注意<span>:</span></strong> 函数重载和默认参数一起使用,需要额外注意二义性问题的产生。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"b4f77e1d-138c-40f9-8132-608440ca1872\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[void MyFunc(string b){\n\tcout << \"b: \" << b << endl;\n}\n//函数重载碰上默认参数\nvoid MyFunc(string b, int a = 10){\n\tcout << \"a: \" << a << \" b:\" << b << endl;\n}\nint main(){\n\tMyFunc(\"hello\"); //这时,两个函数都能匹配调用,产生二义性\n\treturn 0;\n}]]></ac:plain-text-body></ac:structured-macro><h4><span>3.14.2.2 </span>函数重载实现原理</h4><p>编译器为了实现函数重载,也是默认为我们做了一些幕后的工作,编译器用不同的参数类型来修饰不同的函数名,比如<span>void func(); </span>编译器可能会将函数名修饰成<span>_func</span>,当编译器碰到<span>void func(int x),</span>编译器可能将函数名修饰为<span>_func_int,</span>当编译器碰到<span>void func(int x,char c),</span>编译器可能会将函数名修饰为<span>_func_int_char</span>我这里使用”可能”这个字眼是因为编译器如何修饰重载的函数名称并没有一个统一的标准,所以不同的编译器可能会产生不同的内部名。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"3150063a-22e4-49cc-af3e-f3e43096e691\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[void func(){}\nvoid func(int x){}\nvoid func(int x,char y){}]]></ac:plain-text-body></ac:structured-macro><p>以上三个函数在<span>linux</span>下生成的编译之后的函数名为<span>:</span></p><table class=\"wrapped\"><colgroup><col /></colgroup><tbody><tr><td><p>_Z4funcv //v 代表<span>void,</span>无参数</p><p>_Z4funci //i 代表参数为<span>int</span>类型</p><p>_Z4funcic //i 代表第一个参数为<span>int</span>类型,第二个参数为<span>char</span>类型</p></td></tr></tbody></table><h3><span>3.14.3 extern “C”</span>浅析</h3><p>以下在<span>Linux</span>下测试<span>:</span></p><table class=\"wrapped\"><colgroup><col /></colgroup><tbody><tr><td><p>c函数<span>: </span>void MyFunc<strong>(){} ,</strong>被编译成函数<span>: </span>MyFunc</p><p>c++函数<span>: </span>void MyFunc<strong>(){},</strong>被编译成函数<span>: </span>_Z6Myfuncv</p></td></tr></tbody></table><p><span> </span></p><p>通过这个测试,由于<span>c++</span>中需要支持函数重载,所以<span>c</span>和<span>c++</span>中对同一个函数经过编译后生成的函数名是不相同的,这就导致了一个问题,如果在<span>c++</span>中调用一个使用<span>c</span>语言编写模块中的某个函数,那么<span>c++</span>是根据<span>c++</span>的名称修饰方式来查找并链接这个函数,那么就会发生链接错误,以上例,<span>c++</span>中调用<span>MyFunc</span>函数,在链接阶段会去找<span>Z6Myfuncv</span>,结果是没有找到的,因为这个<span>MyFunc</span>函数是<span>c</span>语言编写的,生成的符号是<span>MyFunc</span>。</p><p>那么如果我想在<span>c++</span>调用<span>c</span>的函数怎么办?</p><p>extern "C"的主要作用就是为了实现<span>c++</span>代码能够调用其他<span>c</span>语言代码。加上<span>extern "C"</span>后,这部分代码编译器按<span>c</span>语言的方式进行<strong>编译</strong>和<strong>链接</strong>,而不是按<span>c++</span>的方式。</p><p><strong>MyModule.h</strong></p><p><br /></p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"3cd24cab-d93f-4803-ad43-3068f2dcf917\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[#ifndef MYMODULE_H\n#define MYMODULE_H\n\n#include<stdio.h>\n\n#if __cplusplus\nextern \"C\"{\n#endif\n\n\tvoid func1();\n\tint func2(int a,int b);\n\n#if __cplusplus\n}\n#endif\n\n#endif]]></ac:plain-text-body></ac:structured-macro><p><strong>MyModule.c</strong></p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"fad8f0c2-e7db-4bd1-ad8b-517541aced55\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[#include\"MyModule.h\"\n\nvoid func1(){\n\tprintf(\"hello world!\");\n}\nint func2(int a, int b){\n\treturn a + b;\n}]]></ac:plain-text-body></ac:structured-macro><p><strong>TestExternC.cpp</strong></p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"8a7751ac-960a-4d53-bec1-3528c4b0432d\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[#define _CRT_SECURE_NO_WARNINGS\n#include<iostream>\nusing namespace std;\n\n#if 0\n\n\t#ifdef __cplusplus\n\textern \"C\" {\n\t\t#if 0\n\t\t\tvoid func1();\n\t\t\tint func2(int a, int b);\n\t\t#else\n\t\t\t#include\"MyModule.h\"\n\t\t#endif\n\t}\n\n\t#endif\n\n#else\n\n\textern \"C\" void func1();\n\textern \"C\" int func2(int a, int b);\n\n#endif\n\nint main(){\n\tfunc1();\n\tcout << func2(10, 20) << endl;\n\treturn EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro></ac:layout-cell><ac:layout-cell><p><br /></p></ac:layout-cell></ac:layout-section></ac:layout>","representation":"storage","_expandable":{"content":"/rest/api/content/58954439"}},"_expandable":{"editor":"","view":"","export_view":"","styled_view":"","anonymous_export_view":""}},"extensions":{"position":"none"},"_links":{"webui":"/pages/viewpage.action?pageId=58954439","edit":"/pages/resumedraft.action?draftId=58954439","tinyui":"/x/x5KDAw","collection":"/rest/api/content","base":"https://wiki.suncaper.net","context":"","self":"https://wiki.suncaper.net/rest/api/content/58954439"},"_expandable":{"container":"/rest/api/space/2CT","metadata":"","operations":"","children":"/rest/api/content/58954439/child","restrictions":"/rest/api/content/58954439/restriction/byOperation","history":"/rest/api/content/58954439/history","ancestors":"","descendants":"/rest/api/content/58954439/descendant","space":"/rest/api/space/2CT"}} |