现在的位置: 首页 > 综合 > 正文

c++入门学习(函数)

2013年08月30日 ⁄ 综合 ⁄ 共 5301字 ⁄ 字号 评论关闭

1. 函数
所有的函数运行时都会在stack(栈)中申请存储区,该存储区称为该函数的活动记录,在活动记录中为每个参数提供了存储区,参数的存储长度由它的类型决定,参数传递是指用函数调用的实参值来初始化函数参数存储区的过程。
1.1 值传递
C++中参数传递的缺省初始化方法(包括对象的传递)是把实参的值拷贝到参数的存储区中(按值传递)。按值传递时,函数不会访问当前调用的实参。函数处理的值是它本地的拷贝,即活动记录的值,因此这些值的改变不会影响实参的值。一旦函数结束,函数的活动记录从栈中弹出,这些局部的值也就消失了。
指针传递实质上也是值的传递,因为指针变量本身也是一种变量,系统也为在活动记录区为指针类型的参数创建存储区,并用实参指针变量的值初始化该存储区,相当于把指针变量的值传入了函数。如把指针变量p传入函数,函数在活动区存储了该变量的值,相当于创建了p的一个副本_p,函数体内任何改变_p的值不会改变p的值,但是因为p和_p的初始值是一样的,它们都是指向其它某个变量的值,所以改变_p所指向的值,就相当于改变了p所指向的值,但是如果改变了_p本身的值,它是不会影响p本身的值。例子:
void test(int *p,char *p1)
{
*p = 1;
printf(“%x”,&p1);printf(“%x”,p1);//输出参数p1的地址,及p1的值(也是地址)
    p1 = “hello world”;
}
void main()
{
    char *p1 = “hello”;
    printf(“%x”,&p1);printf(“%x”,p1);  //输出变量p1的地址,也p1的值
    int  I = 0;
    int  *p2 = &I;
    test(p2,p1);
    cout << p1 <<”;”<< p2 <<endl;
}
程序执行后输出的值为:hello;1(地址的输出这里省略了,参数p1和实参p1的地址是不同的,但它们的值是相同的)。为什么一个值变了,另一个没变呢。因为在函数test中,语句p1=”hello world”,实质上是改变了函数活动记录中参数p1的值,使得参p1与实参p1指向的对象不同了。所以要使用指针做为参数实现输出参数的功能,必须用指针的指针,如:
void test(int *p,char **p1)
{
*p = 1;
    *p1 = “hello world”;   
}
void main()
{
    char *p1 = “hello”;
    int  I = 0;
    int  *p2 = &I;
    test(p2,&p1);
    cout << p1 <<”;”<< p2 <<endl;
}
这时输出的就是hello world;1。这个程序中,在test函数体内改变了(*p1)指向内存空间的值,即改变了实参的值,实参的值是一个地址,所以也就是使实参p1指向了另一个字符串。这里还有另一个需要注意的就是,在函数体内让实参指向的是一个字符串常量,字符串常量的内存空间在常量数据区,所以在函数执行完毕后它不会被释放。如果在函数体内让实参指向函数的栈空间,程序会出错的,因为函数的栈空间在函数执行完后就失效了,所以,以下代码是有错的:
void test(char **p1)
{
    char a[10] = “hello world”;
    p1 = a;;
}
另外也可以在函数体内创建一个堆空间,让实参p1指向该堆空间:
void test(char **p)
{
    *p = new char[10];
    strcpy(*p,"hello world");
}
如果这些代码是自已调用的,不要忘了在函数体外释放堆空间的内存,如果是别人调用的,那就有造成内存泄漏的危险,因为别人不知道你的代码是怎么写的,他可能只关心实参p1指向的字符串是什么,它不知道在函数体内p1指向的空间是new申请的,还是malloc申请的,而且也有可能是p1指向的是一个常量字符串(不需要释放),所以即使他想释放没无法正确的释放这些空间。
还有另一种方案就是将指针的引用做为参数,如:
void test(char* &p)
{
    strcpy(p,"hello world");
}
这样就把内存分配的工作交给了调用者,调用者可以把栈或者堆的内存指针的引用传给函数,当然这咱方法也有一个问题,就是函数本身不知道参数p指向的内存空间到底有多大,当然p指向的内存空间的大小也可以做为参数传入函数,还有一种方法就是在函数体内给指针加大分配的内存(不能重分配,否则指针原来指向的内存将泄漏了)。我不知道是否有函数可以计算指针指向的内存有多大,或者指针指向的内存是堆内存还是栈内存,如果有谁知道,请不妨告诉我(email:8280338@tzenet.com),大家共同进步啊:)。
另外还要提一下,在使用strcpy函数及sprintf函数中的参数p前,p应该已分配了空间,而不能少于第二个参数中字符串所占的空间,如果小于第二个参数所占的空间,那么执行strcpy时可能不会报错,但是在delete p或者free(p)时会报错。
对象作为参数传递时,形参是实参的一个拷贝,也就是说进入函数体后,形参用实参的值进行了构造,这样要求它们所对应的类的拷贝函数是正确的,在C++中会为每个类自动创建四个函数:默认的构造函数、析构函数、拷贝函数及赋值函数。自动创建的拷贝函数是浅拷贝,它是按位来拷贝的,如果一个类的方法中有堆内存的分配,那么浅拷贝使两个指针同时指向同一个资源,析构时同一个内存将被析构两次,如果这样的对象做为参数传递给函数,函数运行完毕后,实参中所在的堆资源也没了。
所以这样的情况需要重载拷贝函数。其实判断是否需要重载拷贝函数,可以看在析构函数是否使用了delete及free,如果使用了就说明在类的方法中使用了堆存,应重载拷贝函数,同样如果重载了拷贝函数一般也要重载赋值函数,重载赋值函数很简单,在调用拷贝函数之前需要判断是否是自我赋值,然后再释放自已的资源后,再调用只要调用拷贝函数即可。
还有指针做为参数传递时,必要时在函数体内使用assert(p!=NULL)来判断,如果是malloc或者new来申请内存,则可以使用if(p!=NULL)来判断。
1.2 引用传递
参数传递的另一种方法就是引用传递,引用传递就是告诉函数,直接访问实参,而不需要函数的活动记录中为该引用参数创建存储区,例子:
void test(char* &p1)
{
cout<<"para p:"<<&p<<endl;
    p1 = “hello world”;   
}
void main()
{
    char *p1 = “hello”;
cout<<"p:"<<&p<<endl;
    test(p1);   
}
程序执行后,两个p的地址是一样的,表示在函数被调用时,并没有为引用参数在栈上申请存储空间。
使用了引用参数,函数体中的操作是直接针对实参的,为了避免误操作,可以使用const修饰符使得函数体内的操作不能对实参进行写操作,如:
void test(const char* &p1);
数组的传递都是指针传递,即把数组的起始地址传递给函数,如:
void test(int temp[])
{
   cout << sizeof(temp) <<endl; //输出的值为4,即一个指针所占的空间
}
1.3 省略号
有时候我们无法列出传递给函数的所有实参的类型和数目,这时我们可以使用省略号(…),挂起类型检查机制。如:
void test(param_list,…);
void test(…);
第一种声明,函数调用时需要对显式声明的参数进行类型检查,对省略号的实参挂起类型检查,在这种形式下参数声明后的逗号是可选的,即可以去掉逗号。
1.4 函数的返回值
缺省情况下,函数的返回值是按值传递的,因此函数调用者得到的是函数中return返回值的拷贝,而原来的值及空间将被释放。因此当函数返回指针和引用时一定要注意指针所指空间是否被释放,或者引用的对象是否已不存在这些问题。如:
vector<string> &test()
{
   vector<string> a;
   a.push_back("hello");
   return a;
}
int main()
{
    vector<string> b =  test();
    cout << (*b.begin()).c_str() <<endl;
    return 0;
}
函数test中的对象a是在函数执行完毕后会被销毁,因此返回该对象的引用是无意义的。因此如果需要返回的是指定对象的引用,那么该对象应在堆空间建立,如:
vector<string> &test()
{
   vector<string> *a = new vector<string>;
   a->push_back("hello");
   return *a;
}
int main()
{
    vector<string> b =  test();
    cout << (*b.begin()).c_str() <<endl;
    delete  &b;
    return 0;
}
一定要记住在堆上申请的内存空间一定要手工释放,如果使用malloc申请的就用free释放,如果用new申请的,那么就用delete释放。因此如果函数是供他们调用的,一定要说明这种情况,让调用者释放内存,否则会造成内存泄漏。
1.5 函数指针
函数代码存放在代码区,每个函数都有地址,指向函数地址的指针称为函数指针。函数指针指向代码区中的某个函数,通过该指针可以访问或者说调用这些代码。
函数指针的定义:
(1) int (* fp)(char a, char b):定义函数指针fp,指向一个具有(char,char)参数列表,返回值为int的函数;
(2) int *(* fp)(char a,char b):定义函数指针fp,指向一个具有(char,char)参数列表,返回值为int指针的函数;
(3) typedef int (*FP)(char a,char b):声明了FP是一个函数指针类型;
(4) FP fun(int,cha):定义一个返回函数指针的函数;
函数指针的赋值很简单,如int (* fp)(char,char); int ff(char,char){…};fp=ff。
函数指针的使用fp(‘a’,’b’)或者(*fp)(‘a’,’b’),第二种方式为了兼容C的形式。
可以声明一个函数指针指向其它语言写的函数,如c语言:
extern “C” void (*p)(int);
(5) 指向类成员的指针:指向类成员的指针定义及使用如下:
int TestClass::*p = &TestClass::m_int; //即基本类型及类的类型都要匹配
TestClass k; k.*p = 3; cout << k.*p << endl;
(6) 指向类函数的指针:定义及使用如下:
//类的类型,参数,返回值类型都一致
int (TestClass::*p)(int i) = &TestClass::getInt(int i);
TestClass k; (k.*p)(3);
(7) 指向类的静态成员的指针:(假设这里的m_int是静态成员)
int *p = &TestClass::m_int;
(8) 指向类的静态函数成员的提针:
int (*p)(int) = &Test::getInt;
2. 函数模板
2.1 函数模板的定义
函数模板的定义与类模板的定义类型,如:
template<class Type>
Type min(Type a,Type b)
{
    return a + b;
}
同样是使用template<class Type>,也可以使用template<typename Type>,这里定义了函数的返回值类型,参数类型,也可以在函数模板中使用关键字size定义常量,如:
template<typename Type,int size>
Type test(const Type (*a)[size])
{
    Type b = a[0];
    return b;
}
上述代码使用int定义了一个常量,即可以使用非typename/class来定义常量,然后传入数组的引用。
typename还有另一个作用,就是在模板中说明某个参数是一个类型,而不是值,如:
template <class parm, class u>
parm test(parm *k,u value)
{
    typename parm::name *p;
}
这里使用typename说明parm::name是一个数据类型,而不是一个数值,因此整个表达式是声明了一个指针p。如果没有typename说明,parm::name就是parm::name就是类parm中的一个静态变量了,那么整个表达式就是两个值的相乘。
2.2 模板函数的使用
模板函数的使用很简单,和正常使用函数一样,如cout<< min(1,2)<<endl。上述两个模块函数我也不知道怎么用,在vc++中老是出错。
也可以先定义函数指针指向函数模块,然后再使用该函数指针调用函数,如:
int (*p)(int a,int b) = &min;//这里要加上&取地址符号,否则在vc++中通不过
cout << (*p)(1,2)<<endl;

抱歉!评论已关闭.