C++语言程序设计1第一讲——C++与C的关系_第1页
C++语言程序设计1第一讲——C++与C的关系_第2页
C++语言程序设计1第一讲——C++与C的关系_第3页
C++语言程序设计1第一讲——C++与C的关系_第4页
C++语言程序设计1第一讲——C++与C的关系_第5页
已阅读5页,还剩124页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第一讲 C+与C的关系,第一部分 C+的发展历史简介 第二部分 C+对C常规性能的扩充,第一部分 C+发展历史简介,随着面向对象程序设计思想的日益普及,很多支持面向对象程序设计方法语言也相继出现了,C+就是这样一种语言。C+是Bjarne Stroustrup于1980年在AT&T的贝尔实验室开发的一种语言,,它是C语言的超集和扩展,是在C语言的基础上扩充了面向对象的语言成分而形成的。最初这种扩展后的语言称为带类(class)的C语言,1983年才被正式称为C+语言。 Bjarne Stroustrup在设计和实现C+语言时,既保留了C语言的有效性、灵活性、便于移植等全部精华和特点,又添加了面向对象编程的支持,具有强大的编程功能,编写出的程序具有结构清晰、易于扩充等优良特性,适合于各种应用软件、系统软件的程序设计。 C+语言由C语言扩展而来,同时它又对C语言的发展产生了很大的影响,ANSI C语言在后来的标准化过程中吸收了C+语言中某些语言成分。,C+语言是C语言的超集,与C语言具有良好的兼容性,使用C语言编写的程序几乎可以不加修改直接在C+语言编译环境下进行编译。 C+语言对C语言在结构化方面做了一定程度的扩展。 C+的关键字达到63个,运算符有52个。,C+的四大特征:,可完全胜任C的过程化编程; 又可以使用抽象数据类型进行基于对象的编程; 还可以使用继承、多态进行面向对象的编程; 甚至能担负起以模板为特征的泛型化编程。,C编程与C+编程的区别,区别不是指语法(这一点它们恰恰很相似)。而是使用了完全不同的模型:对象模型还是数据模型。而且使用了完全不同的设计思想。 C程序员越是资深,就越是难以跨越感情和思维模式的鸿沟。 “好的C程序员几乎总是可怕的C+程序员”,一直就有一种思维定势:C就是面向过程的,C+就是面向对象的。这错在混淆了语言和编程思想的区别。 语言只是策划、构思的某种实现工具。这工具不止一种,只是有优劣之分罢了。 编程思想不但是产生产品的蓝图,还代表着它的特点。实现某种思想当然可以选用多种工具。总之,语言并不意味着特定的设计模式。,纠正一个错误认识,可执行文件内存映像,可执行文件除了含有可执行代码,还应含有运行时的数据组织信息,用以指导加载。 可执行代码段,存有全局变量、静态变量(对象)、静态成员、符号表等。还有函数和线程使用的栈数据段,尽管尚未分配,但已规划好了。 因为堆是动态申请、动态分配的,故不在可执行代码中预留。 符号表用于存放字面常量被符号化了。该表是只读的,故体现为常。,封装及构造、拷贝构造、赋值、析构 继承及隐含规则 运算符重载 虚函数、抽象类 异常处理 模板设计 STL(容器、算法、迭代子、适配子、函数对象、谓词 ) RTTI、名字空间等等。,C程序员必须学会的新概念,真正认识C+,C+的最初名称叫C with classes。于是不少人就在这一点原地踏步。 但如今C+已发展得如此成熟,增加了若干当初没有的新东西,成为羽翼丰满的多范型编程语言。这种能力与弹性,使C+成为无可匹敌的开发工具。将其视为多种相关语言组成的语言联邦似乎更为确切。准确的说,C+含有四种相对独立的次语言。,C+使程序员能在更高的抽象层面上操作,这将使得软件与机器的距离显著增大。正因为如此,才使得C+程序员能以问题域的“语言”(思考方式)而非机器的“语言”来表达处理思想;这也正是面向过程与面向对象最本质的区别。 “语言”的优劣与程序(软件)的优劣不是一回事。“语言”只是人们说出思想的工具,其优劣表现在使用时是否顺手、便捷、省力。程序的优劣是用工具制造的产品的质量,那是程序员水平高下的表现。,C+引入了比C更多的语法规则,这些概念理论上讲可以搭配组合出各种语法现象,但只有部分是有意义的。其余部分要么是自相矛盾的,要么是没有意义的。即使是合法的,也可能是属缺陷支配的、难以理解的、维护困难的、难以扩充的、无重用价值的。 比如继承是C+ 的强大功能,能提高软件的透明度和可扩充性,但若滥用继承,会导致代价高昂的不良设计。 另外,有一些性能是不能单独使用的,必须相互配合。 决不可天真地认为只要是学会了语法就掌握了一切。,真正懂了C+决不是仅仅会了C+的语法,那仅仅是皮毛,离懂得了面向对象的编程精髓还差得远。关键是能否会按OO的思想思考、设计、使用好C+。 有三种“会了”的境界: C+的C水平; C+的OO水平; C+的高级应用水平;,掌握C+的三个台阶: 第一层台阶:掌握C+的各种冷僻关键字的含义; 第二层台阶:掌握类、函数、模板及各种规则的语义(用它究竟要达到什么目的,使之具有什么功能); 第三层台阶:掌握STL的全部内容和用法;,语法是语言规则的体现、是生硬的规则、是表象。它不解答缘由,也不指导设计,更不会产生创意和思想。 学习程序设计语言,十之二三是掌握语法规则,十之七八是掌握概念原理,进而掌握指导解决实际问题的规律和方法。 语法是可以查手册、网上求得帮助的,而思想、规律、方法则无处可查。 “首先掌握语言的特征及使用方法,再学习具体的语言实现,然后才是使用环境IDE,这才是语言学习的正道!” (林锐语),15,语法、语言,语言是它所使用的特征集,其中包括了语法规则。 语言实现则是体现语言特征并支持特定编程模式的技术和工具,是语言的一个映射、一个版本实现,并出自于具体的生产厂商。 商家往往根据语言标准,结合具体的硬件环境,用自有技术来实现自己的解决方案,或者扩展了,或者修改了语言。于是语言实现出现了差异,互不兼容,包括代码不兼容、特征实现不兼容、库不兼容、编译器和链接器不兼容、所生成的中间文件不兼容 我们讲的是语言,虽涉及到实现和IDE,但重点在语言本身。,16,语言和语言实现,编程如同写文章,首先你要使用一种语言(如同程序设计语言),写出来的文稿如同代码,还要使用各种工具,如桌子、笔、字典、橡皮等(如同开发平台),文稿写出来要校对、排版、印刷(如同代码调试、编译链接),最后发行(程序发布)。 开发环境 泛指支持软件开发的所有工具,如操作系统、编辑器、预编译器、编译器、链接器、调试器等等。 集成化开发环境IDE则是把这些工具有机地集成到一个软件平台上。,17,开发环境 IDE,操作系统,18,计算机硬件(CPU、Memory、外设),跟踪器,编辑器,预编译器,链接器,编译器,程序员,调试器,剖视 器,运行器,一本普通的C+书(C也一样) ,只能叙述C+的语法,且能保证基本没讲错; 一本好的C+书,能教会你语法和语义,但可能使你误以为“这就是C+的全部”; 一本非凡的C+书,能开阔你的眼界,使你由“知晓”上升为“理解”,甚至是“顿悟”。 多阅读,才能精通,否则只能陋陋寡闻。,程序的开发过程,若不读上十几本名著,想成为编程高手,怕是难! 林锐,程序的开发过程,忠告:,入门级C+ 语言教程; 初级高质量程序设计指南C/C+语言 中级 C+ Primer ;Effective C+; Thinking in C+; 高级C+ Programming Language ; Inside The C+ Object Model;,程序的开发过程,标志性读物:,要想由熟练到达精通, 必须洞悉各种技术背后的实现细节。 因此,本课着重探讨表象背后的技术细节,以揭示其内幕。,程序的开发过程,大师的告诫:,要想学好C/C+,真正成为编程高手,首先是:定要了解内存、了解变量/对象/常量/函数/成员以及指针/引用的内存映像 ,和内存分配过程,建立起内存初始化的意识,清晰地把握变量/对象/指针/引用等在某时刻的状态,以便于对其采用正确地操作。这是学好其他更难的知识的基础。,程序的开发过程,对初学C/C+的忠告,直到你对软件工程的思想产生共鸣、开始浮想联翩时,才算刚刚懂得了程序设计,才刚刚步入IT的殿堂。这之前的日子里,你还在大门外徘徊。 要想尽快进入大门,“无它,唯手熟耳”。,程序的开发过程,对初入行新手的忠告:,“越是深入了解,越能高明地使用。” 这是屡试不爽的真理。当然对于C/C+语言也不例外。这也是我们对待本课的思路。因此我们将深入挖掘语言背后的内幕,而不是仅仅了解语法。并且要涉及到计算机原理、数据结构、操作系统、编译原理、软件工程等相关的课程。,程序的开发过程,本课程的理念:,第二部分 C+对C常规性能的扩充,1. 基本输入流和输出流类 2. 变量及常量 3. 引 用 类 型 4. 函 数 5. 函数重载 6. 带默认形参值的函数 7. new和delete 运算符 8. 行 注 释,1. 基本输入流和输出流类,除了C语言的标准输入输出外,C+语言又提供了类层次结构的输入输出流类库。完整的C+输入输出流类库在很多书籍中介绍,这里不再详述。 为方便讲解中的程序举例,我们简单介绍C+中最常用的基本输入流和输出流。,C+语言把设备之间的信息交换称作“流”是非常形象的。外部设备到计算机的输入信息和计算机到外部设备的输出信息就像是一条条的水流。,屏幕,流,C+语言把设备之间的信息交换称作流,把实现设备之间信息交换的类称作流类,把按面向对象方法的许多个流类构成的流类族(层次集合)称作流类库。C+语言给用户提供了功能完整的、组织成类层次的、可方便扩充的流类库。流类库中的每一个流类定义了设备之间一种方式的信息交换。,流库中iostream类是最常用的基本输入输出流类。iostream类是基本输入类istream和ostream多重继承派生出的。iostream类中包括了键盘输入类、屏幕输出类和错误信息输出类。cin、cout和cerr分别为键盘输入类、屏幕输出类和错误信息输出类的系统默认对象。cin对象键盘输入的运算符为“”,叫“提取”;cout对象和cerr对象屏幕输出的运算符为“”,叫“插入”。,例如: #include / 包含iostream.h头文件 void main(void) char name30; cout name; / 输入 cout “name n“ name endl; / 多个输出 ,上例中,第一行用include语句包含了iostream.h头文件。cin是键盘输入类的系统默认对象,它的输入操作的运算符为,它的参数为变量name。运算符可看作函数的另一种形式的表示,所以运算符也可以有参数。cout是屏幕输出类的系统默认对象,它的输出运算符为。 输入运算符和输出运算符都允许一个对象连续多次使用。endl是换行操作符,每执行一次endl操作符换一行。转义字符n的功能也是换一新行。,与C语言的输入函数scanf( )和输出函数printf()相比,C+语言的cin对象的输入运算符()和cout对象的输出运算符(和输出运算符来方便地输入输出用户自定义数据类型的数据。C+语言的这些性能极大地方便了用户的程序设计。,2. 变 量,C+语言中的变量和C语言中的变量相比,功能扩充之处主要体现在:变量的定义方法、作用域限定运算符、枚举类型、结构体类型、const类型限定符等。,2.1 变量的定义方法 C语言只允许变量在程序开始处定义,而C+语言允许变量在程序的任何位置定义,这就使得C+语言的变量除全局变量和局部变量外,又增加了块变量。C+语言把用花括号括起来的一块区域称为块。块变量就是定义在某个块中的变量。变量的作用域就是变量的作用范围。块变量的作用域就是该变量定义的由花括号括起来的范围,称作块作用域。块变量在其作用域内是可见的,在其作用域外是不可见的。超出变量的作用域使用变量时,由于变量是不可见的而出错。,void main(void) int ii, jj, tt, v(6); for(int i = 0; i 10; i+) for(int j = 0; j i ; j+) int t = 1; ii = i; / VC6下变量i使用正确 / jj = j; / 变量j超出作用域出错 / tt = t; / 变量t超出作用域出错 ,变量ii,jj,tt的作用域是整个主函数;变量i的作用域是从外层循环的for语句处到外循环末尾;变量j的作用域是从内循环的for语句处到内循环结束处;变量t的作用域是从定义处到外循环结束处。,微软的解释是,在循环语句(比如for语句)中定义的变量,由于处于花括号外,因此其作用域不是在循环语句的花括号内,而是在花括号外。 另外,C+语言允许在定义变量时用括号格式赋初始值,如v(6),即变量v的初始值为6。 int v = 6;此时的 = 不可读作等于,而是将整形变量v初始化为6。,“声明”是向系统报个到,仅仅是通知; “定义”是要分配空间的。可以伴随着初始化动作。 int a; /在标准C中这叫声明,在C+这是定义 int * p; / 同上 int A10; / 同上 extern int b; /在标准C和C+中这都叫外部变量声明 struct A; /在标准C和C+中这都叫前置声明 void fun ( int , float ); /在标准C和C+中这都叫函数原型声明,38,声明与定义,全局变量的初始化、表达式中的隐含类型转换、类中隐含成员的初始化等都是编译器的责任。 局部变量的初始化、表达式中的强制类型转换、类中非静态成员的初始化等都是程序员的责任。 编程时,程序员千万不要忘记自己的责任。,变量的初始化,NULL与整数0的区别:区别在类型上。 NULL的类型是void *,不是整型。 bool类型为了符合“最小编址原则”,故占用一个字节(各编译器有别)。 在标准C中,凡没指明的函数形参和返回类型一律默认为int。 C+不支持默认类型概念。好的编程风格是明确指出任何所使用的类型,不使用默认。,请留意类型,void 是其大小无法确定的类型,不可以用sizeof 来测量,只能用于指针和函数。 标准C允许非void *与void * 之间相互转换; 如可以将int * 转换成void * ,再将void *转换成 double *。C编译器认为合法,但这里悄悄发生了内存的扩张或切割,埋下了隐患。 在C+中可以把任意类型的指针指派给void类型的指针只寄存了地址;但不可以把void类型的指针指派给非void类型的指针,除非强转;因为非void类型的指针对地址的“意会”很可能出现偏差。,2.2 作用域限定运算符 C+语言的运算符 : 称作作用域限定运算符,用于解决变量的名字冲突问题,主要用于访问一个在当前作用域内被当前局部变量隐藏的外层全局变量。下例中变量count有两个定义,定义在函数外的是整型,定义在函数内的是浮点型。定义在函数外的变量count作用域是整个文件,定义在主函数内的变量count作用域是整个主函数。变量 :count就表示出了在当前作用域内被局部变量count隐藏了的全局变量count。,int count; void main(void) float count; count = 5.5; :count = 3; C+语言的运算符 : 还可用于限定数据成员或成员函数所属的类,这时把运算符 : 称作类名分辨运算符或类名分辨符,这将在以后的章节中介绍。,2.3 C+新增了两个运算符: 运算符 .* 称作直接选定成员运算符,用于通过对象以及指向成员的指针来访问成员的。 运算符 -* 称作间接选定成员运算符,用于通过指向对象的指针以及指向成员的指针来访问成员的。,2.4 关于等号运算符( = ): 在C语言中 = 运算符称作赋值运算符,但也用于对变量或数组以及指针的初始化。一身兼二职,使许多人搞不清其准确含义。 在C+中给予清晰地规范:只作为运算符,不再承担初始化的职能,初始化工作一律由构造函数完成。但由于还要全面兼容C语言,所以又把C的不清晰带了进来。 int a = 30; /此时的等号是完成了 a 的初始化工作,应称为定义符。 C+则提倡写成int a (30); 以示与对象一致。 x = y; /这才是赋值 。明显的标志是:前面没有类型名,2.5 枚举类型 和C语言不同的是,C+语言的枚举类型是一个真正的类型名,即在C+语言中枚举类型可像其它类型一样使用。系统对待枚举变量将像对待其它变量一样。 C+的枚举类型一般形式是: enum 枚举类型名 枚举列表 枚举变量表 ; 其中,enum是枚举类型标识关键字,枚举列表中定义的枚举值对应着符号数字常量,其编号从0开始。枚举变量可以放在以后需要变量时再定义。枚举类型允许变量使用的操作只有一个,即赋值操作。一个使用枚举类型定义变量的例子如下:,#include enum ColorRed, Gree, Yellow; void main(void) Color MyColor; / Color是枚举类型名 MyColor = Yellow; /对枚举变量赋值操作 cout MyColor; / MyColor中保存的值是符号数字常量2 MyColor = 5; /错,不能对枚举变量赋整数值,类型不对。再说值也超出了范围,但并不示错。 ,2.6 布尔类型 C+语言新增了bool类型。 如,在 C语言中可以这样写: int a = 3; / a 的值为3,可视为 true int b = 1; / b 的值为1,可视为 true int c = a + b; / c 的值为4,可视为 true int d = a - b; / d 的值为2,可视为 true 但在C+中如下写法都错 bool a = 3; / a 的值只能为 true bool b = 1; / b 的值只能为 true bool c = a + b; / c 只能接受 true 或false bool d = a - b; / 同上,/ 初始化一个string 对象用来存放搜索的结果 string search_word = get_word(); / 把一个bool 变量初始化为false bool found = false; string next_word; while ( cin next_word ) if ( next_word = search_word ) found = true; / . if ( found ) / 缩写, 相当于: if ( found = true ) cout “ok, we found the wordn“; else cout “nope, the word was not present.n“;,当表达式需要一个算术值时,布尔对象(如found)和布尔文字都被隐式地提升成int(正如下面的例子) false 变成0 ,而true 变成1。 例如: bool found = false; int occurrence_count = 0; while ( /* 条件省略 */ ) found = look_for( /* 内容省略 */ ); / found 的值被提升为0 或者1 occurrence_count += found; ,正如文字false 和true 能自动转换成整数值0 和1一样,如果有必要,算术值和指针值也能隐式地被转换成布尔类型的值0,或空指针被转换成false 所有其他的值都被转换成true。例如 extern int find( const string / 如找到返回该项的指针 if ( found = find( 1024 ) / ok: found = true,对于C+语言新增的bool类型使用时要注意: bool类型只能接受 true 或false ,而这两个值既不是0/1,也不是字符串。因此bool类型的变量只能在程序的语句中对其赋值,不可以从键盘输入。,2.7 结构体类型 C+语言定义结构体类型的方法和C语言的定义方法基本类同,差别主要有下面两点。 (1) C+语言的结构体类型定义和使用更简单一些。结构体一旦定义,就可以直接使用该结构体名定义变量,而不用处处在结构体名前加关键字struct。例如:,#include struct ListNode / 结构体定义 int data; ListNode *next; ; void main(void) ListNode node; / ListNode是结构体名 node.data = 5; node.next = NULL; cout node.data; ,(2) C+语言把结构体看作类的特殊形式,即结构体是将成员默认为公有的类。换句话说,在C+语言的结构体中也是类,也可以像类的定义那样,定义结构体的私有的、公有的成员函数和数据成员。关于结构体中成员函数的定义和使用方法可参看类的成员函数的定义和使用方法。,2.8 const类型限定符 C+语言中,const类型限定符用于限定某标识符在定义域范围内为常量,所限定的常量、函数参数等成为只读的,不能被修改。在C语言编程序时,通常用宏来定义常量,如 #define MaxSize 100; 用C+语言编程序时,通常用const来定义常量,如 const int maxSize = 100; 虽然两种方法均可定义程序中使用的常量,但两种方法有如下两点主要差别:,(1) 宏定义命令定义的常量只是文本替换,如用100替换MaxSize,它没有数据类型;而const定义的常量带有数据类型,如maxSize就是整数类型。定义有数据类型的常量可使系统进行类型检查,从而防止程序出错。 (2) 宏定义命令定义的常量的作用域是从定义处到文件结束,或到用宏定义终止命令#undef取消其定义为止;而const定义的常量可像变量那样定义在文件内、函数内、块内或类内。显然,用const定义常量更加灵活、方便、广泛。,归结起来,const的用途主要有以下六点: (1) 当const类型说明符用于说明变量时,其作用是冻结所定义的变量在定义域范围内为常量。C+要求用const说明的变量必须在定义时初始化赋值,以后不允许再有赋值操作。 const说明的常量和同类型的变量具有单向兼容性。即,const说明的常量可赋给变量,但反之则不允许。如下例中,变量y定义为整型常量,若要把变量赋给该常量将出错。,void main(void) int x = 4; / 定义为变量 const int y = 5; / 定义为常量 x = y; / 给变量x赋值合法 /y = x; / 给常量y赋值出错 ,const说明的形参可被同类型的非const实参初始化。 #include void F1(const int x) cout x endl; void F2( int x) cout x endl; void main(void) const int y = 20; int x = 5; F1(x); / 合法 F2(y); / 亦合法 ,例中,函数F1中可使用常整型和非常整型的实参。 函数F2中的形式参数定义为非常整型,主函数中的y定义为常整型,函数调用时意味着x是可以修改的。 当然改的不是y,而是x.,(2) 当const类型说明符用于说明引用时,则称为常引用。即不能通过引用名来修改原变量的值。 int i = 100; const int (3) 当const类型说明符用于说明指针类型变量时,有三种情况:冻结所定义的指针变量所指向的数据;冻结指针的地址值;或者冻结指针变量所指向的数据和指针的地址值。 对于指针变量的这三种情况,const类型说明符的用法将不同。例如:,void main(void) const int x = 5, y = 6; int m = 10; const int *p1 = /非法操作, 修改指针地址内的值 ,(4) 当const类型说明符用于函数的形式参数时,其作用是冻结实际参数在该函数内为常量,即该参数不允许在函数内被修改。形参可以是const int x或const int 这样的例子将在类与对象的章节中介绍。,使用const的好处: 它允许指定一种语意上的约束某对象不能被修改。编译器将严守这一约束,对函数进行检查。这实际上是函数对调用者所作的承诺。 常量必须在定义时就初始化。,指向常量的指针可用于保护所指的变量/对象: const int *p = new int (100); cout “*p = “*p endl; /delete p; / 错误,因为p是const型,所指的常量不可删 delete (void *) p; /可以这样删除,2.9 函数形式的类型转换 C+语言对变量提供了函数形式的显式类型转换。任何系统定义的基本数据类型或用户自定义的数据类型的类型名,都可以作为函数名使用,从而把变量(或常量)从一种数据类型转换到另外一种数据类型。函数形式的类型转换例子如下: int i = 20; float x = 23.9; i = int(x); 在C语言中,上述例子的显式类型转换方法是: i = (int)x;,3. 引 用 类 型,引用类型是C+语言新增加的一个类型,引用类型用标识符 & 表示。引用是给已存在的变量或对象起一个别名,即引用引入了一个变量或对象的同义词。当建立引用时,程序用另一个变量或者对象的名字初始化它。从那时起,引用就作为目标的别名而使用,对引用的改动实际上是对目标本身的改动。 C+语言增加引用类型主要用在三个方面: (1) 定义变量或对象的别名; (2) 定义函数的参数为引用型; (3) 定义函数的返回值类型是引用型。,引用变量必须在定义时初始化,此后就再也不能改变了,这个意义上讲它已经是常的; 给引用变量赋值,修改的是它所依附的变量本身的内容,而不是让引用改换门庭。这正是引用与指针的区别所在。 int ii = 0; jj = 10; const int / 却可以 另外,不可声明引用的引用、指向引用的指针、void类型的引用、空引用、或引用的数组 Why?,原因有些微妙,需要作些解释: 引用被编译器处理为存放对象地址的指针,不过该指针仅供内部使用。对于不可寻址的值,如文字常量以及不同类型的对象,编译器为了实现,必须生成一个临时对象,引用实际上指向该对象,但用户不能访问它。例如当我们写:double dval = 1024; const int 若允许非const 引用指向需要临时对象的对象或值,则意味着可以通过引用修改常量,显然不合逻辑。而const 引用是只读的,用它无后顾之忧。,引用的初始化方式: #include void main() int i=110; int * p = ,3.1 变量或对象的别名 引用类型用于定义变量或对象的别名,称作独立引用。独立引用主要用于解决大型软件开发中名字空间的冲突问题。一般来说,大型软件开发需要多人合作,由于开发者的起名习惯不同,经常会发生相同的事物起了不同的名字。C语言中一般是采用换名方法解决这个问题,这需要作大量的额外工作。C+语言增加引用类型后,可以将两个本不同的变量名用引用联系起来:定义一个是另一个的引用,这种方法解决名字冲突非常简单:只加一句话。,被引用的对象可以无名,但绝不可是临时对象。 下边的例子中可以看出,引用定义具有传递性,可以定义一个引用,它的初始化是由另一个引用完成的; 另外,可以定义指针类型的引用变量,此时要注意,求地址运算符&和引用标识符&完全一样,但使用的场合是不同的。 引用的操作:对引用的操作就是对其所代表的变量的操作,如果程序寻找引用的地址,则为引用所依附的目标的地址。,例: int i=5; int 引用与指针差别很大,指针是个变量,可以把它再变成指向别处的地址,然而,建立引用时必须初始化,并且不能再依附其他不同的变量了。,# include void main(void) int i = 5; int 更错,int *p = 从C+编译器层面分析,引用其实是用指针实现的。即,所谓指针,就是个无名指针。编译器会将所有用“引用名”的地方都换成“*指针”。,#include void main() float f = 34.5; /同一块内存 int * ip = reinterpret_cast ( ,同一内存的不同类型视角,运行结果: float address : =0x0012FF7C=34.5 int address : =0x0012FF7C =1107951616 the int : 100 the float : 1.4013e-043,3.2 函数的引用类型参数 在C语言中,系统处理函数参数传递的方法是用函数的实际参数初始化形式参数,即函数处理的仅仅是存放在系统栈中的实际参数的副本。这样,当函数结束,系统把存放在系统栈中的形式参数自动释放,这些局部数据值就不复存在了。若是在函数中修改了形式参数,并需要把这些修改值返回给实际参数,就得使用传址。例如,下边设计的swap1( )函数用于交换两个参数的数据值,但它只交换了两个形式参数的数据值,两个实际参数的数据值并未被交换。,# include void swap1(int x, int y) int temp = x; x = y; y = temp; void main(void) int x = 5, y = 6; swap1(x, y); cout “x = “ x n “y = “ y endl; ,C语言中解决上述问题的方法是把变量地址作为参数。当系统把实际参数的地址复制给形式参数的地址后,则函数中对形式参数地址中数据值的修改,就是对实际参数地址中数据值的修改。下边的swap2( )函数就是用变量地址作为参数解决上述问题的。,#include void swap2(int *px, int *py) int temp = *px; *px = *py; *py = temp; void main(void) int x = 5, y = 6; swap2( ,虽然用变量地址作为参数可以解决把形式参数的修改值传回实际参数的问题,但是,C语言的上述指针方法很不直观。特别是当要传递的函数参数本身就是指针时,此时函数参数就要设计成指针的指针类型。如此类推,当要传递的函数参数本身是指针的指针类型,函数参数就要设计成指针的指针的指针类型。这样方法设计的函数不仅复杂,而且可读性很差。 C+语言有了引用类型后,用引用类型解决上述问题非常方便。在进行引用类型参数传递时,系统只是把实际参数的标识传给了引用类型的形式参数,使得引用类型形式参数标识的是实际参数变量(或对象),这样,任何对形式参数的修改就是对实际参数的修改。下边的swap3( )函数就是用形式参数方法解决上述问题的。,#include void swap3(int ,void main(void) int x = 5, y = 6; swap3(x, y); cout “x = “ x endl; cout “y = “ y endl; 比较上述swap3( )函数和swap2( )函数可以看出,swap3( )函数不仅简单,而且可读性好;比较swap3( )函数和swap1( )函数,还可以发现,swap3( )函数和swap1( )函数除了参数类型定义不同外,函数内部的语句完全相同。,用引用传递函数参数 1、引用传递参数 传递引用给函数与传递指针的效果一样,传递的是原来的变量或者对象,而不是在函数作用域内建立的变量或者对象的局部副本。 2、用引用和指针一样,可以修改参数的值,从而达到返回多个值的目的,但比使用指针更方便、快捷。 用const限制引用 传递指针和引用是为了提高效率。当一个数据类型很大时,因为传值要复制副本,所以不可取。另一方面,传递指针和引用存在着传值所不具有的危险。程序有时候不允许传递的指针所指向的值被修改或者传递的引用被修改,但传递的地址特征使得所传的参数处于随时被修改的危险之中。保护实参不被修改的办法是传递const指针和引用。,分析这段程序的执行结果: 比较引用作形参、静态局部变量、函数返回三者的用法 int calculator() int sum1 = 0, sum2 = 0; int aa = 2, ab = 3, ac = 4; sum1 = Area( aa, /此时sum1, sum2是多少? ,int pi = 3; int Area(int r, int *s) int b; static int c =0; b = pi * r * r; c += b; *s = c; return b ; ,3.3 函数的引用类型返回值 C语言中的函数返回值只能作为右值表达式使用,不能作为左值表达式使用。(所谓右值表达式是指赋值操作=的右边,所谓左值表达式是指赋值操作=的左边。) C+语言有了引用类型,就可以设计出能作为左值表达式的函数返回值。常见例子是数组的 运算符重载。在例2中我们设计了数组类Array,支持任意下标( - 32767到32768)的数组定义。数组类Array中重载的运算符 ,其函数返回值是整数的引用类型。现在尚无法完全理解此例子的读者,可在学习完相关章节后,再回头学习它。,返回堆中变量的引用 对引用的初始化,可以是变量,可以是常量,也可以是一定类型的堆空间变量。但是,由于引用不是指针,所以,不可以直接用地址来初始化引用。由于使用的是堆空间,所以用完后要删除,其格式为删除引用的地址。 int *pInt = new int; int ,以下的这种赋值方法正确吗? 1: const A* c=new A; A* e = c; / 不可 2: A* const c = new A; A* b = c; /可 注意:任何时候,当你看到引用名时,都要问自己:它的正名是什么?在那里?,4. 函 数,4.1 函数原型 C语言(而不是C平台)对函数参数不做类型检查,只要求在函数使用以前给出函数返回值的类型的定义即可(这实际上属于变量的先定义后使用)。例如,下边例子中只要在使用函数max之前定义了函数返回值的类型,则系统不会判错。对函数的实际参数和形式参数不做类型匹配检查,经常会引起系统运行出错。,何谓“函数”?,什么是“函数” 完成一定功能的独立完整的代码块 “函数”是用户自定义的特殊运算符。 函数是用来堆砌类的积木块。 类是用来堆砌程序的积木块。 类就是软件的模块化设计的直接产物,也是基本模块 C和C+都允许函数的重复声明(只要声明的匹配)。,#include int max( ); / 定义函数返回值的类型 void main(void) int x = 5, y = 6; int z; z = max(x, y); printf(“z = %d“, z); ,int max(int x, int y) if(x y) return x; else return y; ,C+语言对函数参数做类型检查,它要求任何函数在使用之前,都必须有该函数的定义,并把这种函数定义称作函数原型,系统依据函数原型对实际参数和形式参数做类型匹配检查。函数原型的形式为: 返回类型 函数名(形参表); 上述例子在C+语言中,定义函数返回值的类型时(第2行)应写成如下形式: int max(int x, int y); / 函数原型定义 由于函数原型需要定义的是函数参数的类型和函数返回值的类型,所以写成如下只有参数类型、没有参数名的形式也可以: int max(int , int );,因为C+语言程序均要使用主函数,为方便用户使用,系统在内部给出了主函数的函数原型,所以主函数不需要函数原型,这也是主函数可以放在程序任何地方的原因。 C+语言的函数原型机制有如下两条优点。 (1) 系统根据函数原型对函数参数做类型检查,防止了很多可能发生的错误。 (2) 为下边即将讨论的函数重载奠定了基础。,函数对内存的使用,支持函数运行的内存区叫栈。这是一块公共数据区,供所有函数在调用时临时使用。总量是有限的。 函数栈是在该栈区上动态分配的空间。编译时只是“规划了”函数对该区域的使用设想,并没实施。调用到该函数时,原先的规划设想才得以落实。一旦调用完毕,函数栈立即自动归还给系统,供别的函数使用。,源程序: #include using namespace std; long fac(int n) long f; if (n0) cout“n0,data error!“endl; else if (n=0) f=1; else f=fac(n-1)*n; return(f); ,95,void main() long fac(int n); int n; long y; coutn; y=fac(n); coutn“!=“yendl; ,96,运行结果: Enter a positive integer:8 8!=40320,2,6,2,1,1,24,6,120,24,内存图示:,1,主函数栈,子函数栈,参数传递的本质,别指望“凡是在参数中使用了指针就定能完成传址”。 请看下例: void GetMemory( char *p, int num ) p = (char *)malloc(sizeof(char) * num); void Test(void) char *str = NULL; GetMemory(str, 100); / 尽管p得到了空间,可 str 仍然为 NULL strcpy(str, “hello”); / 运行错误 ,参数传递的本质,在C/C+中,函数在传递参数时的动作永远是拷贝构造。即形参永远是实参的复制品。 对于变量,这拷贝的结果是传值; 对于指针,这拷贝的结果是指针的值,也就是在传别人的地址,于是称其为传址。不过在函数体内要按指针的规律操作它。 当需要传递的是指针变量自身时,此法则不灵了,只能用能传指针自身地址的变量指向指针的指针变量来当形参,把自身的地址当实参。 函数的返回值也是采用复制的方式将处理结果交给调用者的。,引用的不当使用,const string ,name是谁的引用?,此题错误原发于子函数。主函数中的错误是次要的。,改正:可以将形参改为引用(只改形参); 也可以将返回改为对象(只改返回);,4.2 内联函数 函数是模块化软件设计的基本单位,一个软件系统中一般要包括许多函数模块。但系统在进行函数调用时,需要花费额外的空间资源和时间资源,因此,在函数模块太小或函数模块需要被频繁地调用这两种情况下,划分函数模块又是程序的承重负担,对运行效率不利。,为了解决这种矛盾,C语言程序通常采用宏的方法,而C+语言增加的内联函数(也称作内置函数)也为了解决此矛盾,而且更方便。所谓内联函数就是指在编译时被像宏一样直接插入到程序的调用语句处的函数,取代了函数调用,从而减少了运行时函数调用的系统开销。内联函数的标识符关键字是inline,使用方法是在函数的开始处加inline。 为了和内联函数区别,通常也把常规的非内联函数称作外联函数。 请注意,使用inline只是向系统建议,而非强制,最后的决定权在于编译器 。,内联函数的认定还有多条限制: 内联函数中不可含有循环; 内联函数中不可含有swiych语句; 内联函数中不可含有静态变量; 内联函数不可为递归函数; 内联函数中不可含有异常处理。 另外,内联函数的函数代码不要太长,如果太长,该内联函数在程序中又被多次调用,则可能引起整体代码膨胀,后果是严重的。,C+语言的内联函数和C语言的宏相比,内联函数的主要优点是对参数类型进行一致性检查,可早期发现许多编码错误。 在类设计中,类的成员函数的内联函数可有两种表示方法:一种方法是如上所述的加标识符inline,另一种方法是把函数体直接写在类定义内。前边的例2的类Array的构造函数、析构函数和重载的运算符 函数,都是把函数体直接写在类定义内的,因此都属于内联函数。,inline有两种方式: 在类定义中实现函数(隐含内联) class A public: A(). ; 在类外实现时加inline (显式内联) (推荐) class A public A(); ; inline A:A() . 在函数的执行时间小于或相当于函数调用的执行时间时,一般代码行是5行以内的,宜用inline。,内联函数的使用须知,内联函数无法随程序库进行版本升级。一旦改变了内联函数的功能,使用它的用户们就必须重新编译、链接应用程序。 大部分调试器对内联函数束手无策。因为内联函数被编译器展开后,已经不再是函数的样子,已无法跟踪调试。所以开发平台对调试版的源代码不做展开。,将同一个名字用于同一作用域的不同类型的多个函数的情况叫重载。 什么样的函数是重载函数? 只有signature(函数名、参数和函数的const属性) 不同才是重载,否则认为是相同的函数。signature 不包括返回值,参数的缺省值。 函数前缀的访问控制不同也不是重载,而是相同的函数。 在不同的非名字空间作用域声明的函数不是重载。,5. 函数重载,class A public: void f( int ii); / 重载 void f( ); / 重载 void f( int ii) const; / 重载 int f( int ii); / 不是重载,是重复定义 virtual void f( int ii); / 不是重载,是重复定义 private: void f(); /不是重载,是重复定义 ;,函数的内部名称Name-Mangling技术,对所有的标识符,VC+都会将其替换成内部名称。如对于,函 数 重 载,struct Sample_1 int count; char title; ;,struct Sample_2 int count; char title; ;,会将 struct Sample_1的成员count改名为 _Sample_1_count; 将struct Sample_2的成员count改名为 _Sample_2_count;,由于编译器要求可执行文件中的符号名的唯一性,函数亦不例外。于是将函数形参的类型名加入函数名中,打造成函数的内部名称,于是就避免了函数的同名之虞。如函数,函 数 重 载,int fun( int a , int n) int sum = 1; for(int i=0; in; +i) sum*=a; return sum; ;,会被改名为 _fun_int_int 。 可见Name-

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论