C/C++的函数

这篇介绍一下C/C++中的函数,主要是函数指针和传参(在文章的后半部分)相关的讨论

如有侵权,请联系删除,如有错误,欢迎大家指正,谢谢

函数的作用主要是为了方便代码复用

0. 函数调用

  • 函数调用的本质:函数地址+(参数列表)
  • 函数名与数组名的作用类似,一个函数为 void fun(int a); 通过fun(a); 或者&fun(a); 都可以成功调用函数,也就是说函数名或者是对函数名取地址意义是一样的
  • 在C中,函数如不接受任何参数,必须在函数的参数列表处写void,函数列表啥也不写表示参数个数不能确定
  • 在C++中,参数列表啥也不写默认是void,不接受任何参数,函数的调用跟C是一样的

1. 函数声明

  • 函数可以不声明,直接定义在主函数前面,这样可以被主函数正常调用,但是如果定义的函数去调用其他的自定义函数,就要注意函数的位置,函数只能调用定义在它前面的函数,不能调用定义在它后面的函数,这样的话,定义函数还要考虑它们定义的位置就比较麻烦了,所以声明函数就可以解决这个问题,声明可以写在所有函数的前面,也可以写在其他位置,但是如果声明在其它位置,那么这个函数还是不能被自由的调用,声明也就失去了它的意义,所以最好是将函数的声明写在所有函数的前面

2. 函数返回指针

  • 函数的返回类型如果是指针变量,这个指针变量指向的空间一般应该是堆区的地址空间,因为如果返回的是指向栈区空间的指针,在函数执行结束后,这个栈区空间(局部变量)有可能生命周期已经结束,被系统释放掉了,虽然有时候可以正确访问到数据,但是很容易造成访问野指针的错误,因此,最好是返回指向堆区地址的指针,需要注意的一点是,返回堆区地址的指针用完之后,记得要释放掉,避免造成内存泄漏

3. 函数类型

  • 函数的类型是由返回值类型,参数类型和参数数量确定的
  • 如函数原型是void fun(int a, double b){ ; } 这样的,它的声明可以写成void fun(int, double); 这条语句表明了函数的返回值类型,参数类型和参数数量可以确定一个函数的类型
int fun(int a, double b) {
​	cout<< a << " " << b <<endl;return 0;
}

int main() {int a = 1;double b = 1.2;int (*p)(int, double) = fun;  // 定义一个函数指针,满足三点要求返回值类型,参数类型和参数数量int (*q)(int, double) = &fun; // 还可以这样写,与上面一条语句意义相同,因为fun和&fun都指向函数的首地址,这与数组名的含义类似       
    // 以下 6 种方式均可成功调用函数                        fun(a, b);&fun(a, b);p(a, b);
    (*p)(a, b);
    q(a, b);(*q)(a, b);
}
  • p指向的是函数fun的地址,p(a, b); 满足函数调用地址+参数列表的规则,(*p)(a, b); p指向函数地址,( *p)就是函数本身, ( *p)(a, b); 也就相当于fun(a, b);也符合函数的调用规则

4. 函数参数不确定的情况

  • 形式:void fun(int n, …);
  • 这样声明的函数可以实现参数个数不确定时的函数声明或定义,其中n表示参数的个数,后面三个点是可能的参数个数
void fun(int n,...) {
​	va_list ap;     // 定义一个保存参数的数组__va_start(&ap, n); // 将参数装入数组中,ap是保存参数的数组,n是参数个数
​	cout << __crt_va_arg(ap, int) << endl;   // 从参数数组中取数据,是按照参数类型往取的,且需要按照输入的顺序取
​	cout << __crt_va_arg(ap, double) << endl;
​	cout << __crt_va_arg(ap, int) << endl;
}

int main() {int a = 2, b = 3;double c = 3.4;fun(3, a, c, b);system("pause");return 0;
}

注意

  • 在取参数的时候是按照参数的类型取的,并且要与出入的参数顺序相同,比如第一个传int,第二个传double,取的时候也必须是第一个取int,第二个取double,在去参数的时候类似于出队列的操作,出一个就从队列中删除一个,但具体底层是如何实现的还不是很清楚,只知道与出队列的操作类似,FIFO原则

5. 传值 & 传址 & 传引用

传值

  1. 传值时,形参是实参的拷贝,改变形参的值不会影响外部实参的值,从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出
  2. 当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递
  3. 值传递对于较小的数据量比较方便,但是如传递结构体等数据量比较大,进行拷贝会造成比较大时间和空间的开销
  4. 还有一点也是最重要的,在的值传递时,会进行拷贝构造,这里的拷贝构造是浅拷贝,对于需要深拷贝的还需要重写深拷贝,有一点复杂

传址

  1. 形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作
  2. 当传递的数据量比较大时,采用传址的方式会节省时间和空间的开销,为防止对实参的修改,可在形参前加const,构成底层const保护实参数据
  3. 数组作参数时,会退化成指针,传递二维数组,(*p)[3]第二维不能省略(在上一篇文章最后有介绍C/C++的指针)
  4. 指针的一个很大的优势是使用灵活方便,但是这也是它的一个缺点,容易造成内存的越界访问,造成一些意想不到的危险,接下来的传引用可以避免这个问题

传引用

  1. 形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作
  2. 在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址,被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量,正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量(这篇文章篇幅已经很长了,引用的更详细的介绍在后面会专门出一片文章)

参考文章:
[1] C++ 值传递、指针传递、引用传递详解
下一篇:C/C++内存申请和释放(一)

发布了20 篇原创文章 · 获赞 3 · 访问量 495

猜你喜欢

转载自blog.csdn.net/xiao_ma_nong_last/article/details/103387918