C++复习4.函数设计基础

时间:2021-05-09 23:31:20

C/C++ 函数设计基础 20130918

函数式程序的基本功能单元,是模块化程序设计的基础,即使函数的功能正确是不够的,因为函数设计的细微缺点很容易导致函数被错用。

了解函数的基本知识,堆栈和堆的相似点和不同点;函数的接口设计和内部实现,断言、const,存储类型,递归函数等等。

1.认识函数

函数分为库函数和用户自定义的函数,两者在本质上是一致的,知识前者是预编译好的。对于静态链接的函数库或者类库,如果你调用其中的函数,那么连接器慧聪相应的苦衷调去这些函数实现的源代码并且把它们连接到你的程序中。如果你没有使用库函数,则不会讲他么连接到你的程序中。如果你是用的是动态连接库(DLL),运行的时候需要将所有的DLL复制到运行环境的目录中。连接的本质就是把一个名字实现绑定到对他的每一个引用语句上面,如果你没有调用自己编写的函数,那么是不会为该函数生成任何可以执行的代码。

2.函数原型和定义

C语言中存在着函数前置声明的概念,因为在C语言中,同一个作用域中不能出现同名的全局函数,所以前置声明足矣表明一个函数定义的存在性和唯一性。

形参和实参:在函数原型或者地宫一以及catch中的参数列表中的被生命的对象,指针等等;实参就是实际调用函数的时候的参数列表。

函数调用的实际就是用实参来初始化形参而不是替换形参,在声明函数的时候,最好将参数的名称给出,虽然编译器会忽略他们,便于阅读。

3.函数调用的方式

一般函数都支持三种调用方式:像过程一样调用(表达式中调用),嵌套调用和递归调用。如果函数咩有返回值,则不能够在表达式中使用。还用一种不疆场使用的方式就是回调函数。

4.认识函数的堆栈

函数的调用必须通过堆栈来完成,函数堆栈实际上使用的是程序的堆栈段内存空间,虽然程序的堆栈是系统为程序分配的一种静态数据区,但是函数堆栈确实在被调用的时候才动态分配的。也就是说,我们不可以在编译的时候就为函数分配好一块静态空间作为堆栈。因为无法在编译的时候确定函数运行时所需要的堆栈的大小;而是函数在调用结束之后如果还保留他的堆栈空间,就会造成内存空间浪费,而且会他被耗内存,不切实际。

堆栈是自动管理的,局部变量的创建和销毁,堆栈的释放都是韩侯蔌自动完成的,不需要程序员的干涉。局部变量在程序执行刘到达它的定义的时候创建,在退处所在的程序快的时候,自动清退,将空间返还给程序。显然堆栈是在预先分配好的内存空间上创建的,不需要运行的时候在搜索,因此这样比动态内存分配空间要快而且更加安全。这就是堆栈和堆的区别。

函数堆栈的三个主要用途:在进入函数前保存环境变量和返回地址,在进入函数的时候,保存实参的拷贝。在函数体内部保存局部变量。

5.函数的调用规范

函数调用规范决定了函数调用实参压栈,退栈,堆栈释放的方式,以及函数名的改编(Naming-Mapping)的方案。

__cdecl: C/C++ 函数默认的调用规范,参数从左向右传递并且压入栈中,由调用函数负责堆栈的清退,因此这种方式方便传递个数可变的参数给被调用的函数。

__stacall: WinAPI的调用规范。参数从右向左一次传递并且压入栈中,用调用函数负责堆栈的清退。

__thiscall: C++ 非静态成员函数的默认调用规范,不可以使用个数可以变化的参数。当调用非静态函数的时候,this指针直接保存在ECX寄存器中而非压入函数的堆栈中。

__fastcall: 该规范所修饰的函数的实参将被直接传递到CPU寄存器中而不是内存的堆栈中。堆栈的清理有被调用函数清退。该规范不可以用于成员函数。

6.函数的连接规范

使用不同的编程语言,进行软件的联合开发,需要统一函数、变量、数据类型、常量等等的连接规范( Linkage Specification),特别是不同的模块之间共享的数据接口部分,当开发程序库的时候,需要明确连接规范。

对于静态链接库lib和动态链接库dll中的全局数据类型,全局函数,全局变量,他的连接规范必须在两端保持一致,否则会出现连接error,这是因为普通封装的DLL的函数库湖或者类库,客户端在创建的时候一般需要与他们的导出库(.lib)进行连接,除非使用loadLibrary和getProcAddress() 来获得dll中的函数地址。

7 函数参数传递

函数接口的两个要素:参数和返回值,在C语言中函数的参数和返回值的传递方式有两种:值传递(Pass by value)和地址传递(pass by pointer),在C++中增加了引用传递( Pass by reference).引用传递的效果和指针传递的效果相似。不论函数的原型还是定义,都需要明确写出每一个参数的名字和类型,如果参数的列表是空的,最好加上一个void,这是因为标准的C会把空的参数列表解释成可以接受任何类型和个数的参数,但是对于标准的C++则会把没有参数列表解释成不可以接受任何类型的参数。

如果参数是指针,且仅作输入用,应该在指针上面加上const,防止通过该指针修改指向内存单元的数据。如果输入的参数以值传递的方式传递对象,则易改用“const & ”方式传递,因为引用的创建和销毁不会调用对象的构造函数和析构函数可以提高效率。

8返回值得规则

函数返回值的方式有两种:使用return语句返回和使用输出参数。尤其是当函数的返回值不止一个的时候,仅仅使用return是不够的。如果没有返回值,函数声明返回值的类型是void。标准的C程序,如果不指定返回值类型,一律按照int方式处理;C++中函数必须声明返回值得类型.

9 函数内部的实现机制

不同的功能的函数,其内部实现的各不相同, 我们需要在函数体的入口处和出口处把关,也提高函数的质量.

很多的程序出错是因为非法的参数传递,所以我们应该充分理解并且正确使用断言(assert), 在return中正确性进行检查。同时return语句不可以返回指针,指针指向的内存空间是在堆栈的内存中或者是堆栈内存中的数据引用。因为这些数据会在函数结束的时候自动释放。

char * func(void){

char str[] = “hello world”;//store data in the stack,

cout << sizeof(str) << endl; // 12

cout << strlen(str) << endl; //11

return str;// str指向的单元被释放,存在风险,不是我们想要的结果

}

char * func( void){

char * str = “hello world”;// right store data in static data area

return str; //这样是可以的,但是程序没运行一次,就会创建一个

}

return String(s1+s2);

String result(s1+s2); return result;

后面这一种方式比较低效:这样会做三件事情,result对象被创建,同时调用相应的构造函数完成初始化;然后调用拷贝构造函数,把result赋值到保存返回值得外部存储单元中;最后result对象在函数结束的时候,销毁对象,调用析构函数。但是第一种方式直接创建一个临时变量并且返回,编译器可以直接把临时对象创建并且初始化,在外部存储单元中,省去了拷贝构造和析构函数的开销,提高了效率。

函数内部的static变量是用记忆的功能。

13 使用const提高函数的健壮性

const更加强大的作用就是修饰函数的返回值和参数,甚至是函数的定义体。

Const修饰的东西都是受C/C++语言实现的静态类型安全检查机制的保护,可以预防意外的修改,提高程序的健壮性。 Use const whenever you need

1)       使用const修饰参数

参数用于输出,不论他使用什么数据类型,也不论采用指针传递还是引用传递,都是不可以加上const修饰的,否则将失去输出的工呢过,const只可以修饰输入参数。如果还想保护指针本身,可以声明指针本身是常量,放置该指针的值被改变。

void OutputString( const char * const pStr);

如果采用值传递的方式传递的参数,因为是默认创建的值得拷贝,所以技术在程序中修改了参数的值,也不会影响原来程序中的值,没有必要天剑const修饰。为了提高效率可以修改参数传递的方式 为引用传递,不会产生临时变量,但是可能会修改原来的参数的值,我们只要使用const修饰即可:

void func(const int & a); 但是对于基本的参数类型好似没有构造函数和析构函数的,所以没有必要。但是对于ADT数据结构,推荐使用这种方式。

2)使用const修饰函数的返回值

如果给指针传递的韩侯蔌返回值加上const,那么函数的返回值是一种契约性常量,不能够别直接修改,并且该返回值只可以被赋值给加上const修饰的同类型的指针,除非强制性转换。

const char* getString(void);

char * str = getString();//编译出错

const char * str = getString();// right way