- 1.简单介绍库函数
- 2.函数返回值
- 3.函数参数的自动转换
- 4.参数的传递
- 5.const
- 6.函数和结构
- 7.函数指针
首先,什么是库函数?
库函数就是已经定义好的函数的一个集合,也就是说这个文件里面有很多函数的原型,你只需要通过函数名和参数来调用它们即可。
而库函数就在头文件所在的位置,比如
函数根据返回值可以分为两种函数:
- 有返回值的函数
- 没有返回值的函数
有返回值的函数
int max(int x,int y)
{
if(x>y)
return x;
else
return y;
}
没有返回值的函数
void max(int x,int y)
{
if(x>y)
printf("x大于y");
else
printf("x小于y");
}
主要讨论有返回值的函数,对于有返回值的函数,如何正确的区了解它呢?
首先,要知道一些的概念:栈帧
首先,先说一下什么是栈帧
在C里面,当运行一个函数的时候,系统会在内存的栈空间中开辟一个栈
。这个栈存放函数的变量,返回地址等。这个空间就叫函数栈帧。严格遵循先进后出的原则。
那么当调用函数的时候,新的变量加进栈帧,当函数结束的时候,实际上栈的顶部并没有出栈,所谓的局部变量还是在栈顶,只是将其设置为无效数据。直到下一次调用函数,新的变量覆盖原有的栈帧。那么宏观上来看,之前结束的函数的变量确实不复存在了。但是该函数的返回值确可以及时传递出去。
说的通俗一点:有返回值的函数在函数结束的时候,将其返回值临时保持在一个新的内存中,然后函数销毁(其变量啥的都销毁),然后将临时返回值赋值给新的变量,然后这个临时变量销毁。而实际上销毁额函数的局部变量的值仍然在栈帧中,但是却是无效数据了,之后再有变量就会把这个区毫不留情的覆盖。
(这一块讲的不好,之后学习了汇编语言再去重新学习一下)
3.函数参数的自动转换调用函数常常需要实际参数,那么如果实际参数的类型和形式参数的类型不一样,这会发生什么?用一个小程序来实验一下
int max_c(double x, double y)
{
if (x > y)
return x;
return y;
}
int main()
{
int a, b;
a = 5, b = 3;
double c = max_c(a, b);
cout << c;
return 0;
}
a,b都是int类型,但是max函数的形参都是double类型,通过调试发现,当a,b传入max函数时,a,b将会自动转换成double类型。又注意到max函数的返回值是int类型,那么又会将double类型转换为int类型,最后将返回值复制给c,但是c又是double类型,则又要将max函数的返回值转换为double类型。最终的结果就是double类型。当然这些转换都是自动完成的。
仅当转换又意义的时候,原型才会导致类型转换,比如原型不会将整数转为指针。
4.参数的传递对于函数的参数的传递方式,有传递值和传递地址。
int max(int x,int y)
{}
像上面的函数,当调用max(a,b)的时候,是将内存将会拷贝a,b,生成新的两个局部变量,然后在max函数里面的x,y即是新的变量,和a,b无关。
void print(int a[],int n)
{}
而像这种形式,当调用函数的时候,内存不会将整个数组拷贝一份,而是将数组的地址传递给函数。这样可以节省内存空间。但是,由于传递的值是地址,那么在调用函数中改变了数组的值,那么主函数中的数组值也会改变,也就是说调用函数和主函数用到的数组是一个数组,占用同一个内存空间。
5.constvoid show_array(const int a[],int n){}
上面这个函数意味着a指向的数据常量数据,这说明不能使用a来修改该数组。,比如:a[1]=1,这种尝试更改数组的行为是禁止的。换句话说,a[0]…a[n-1]是常量,不能修改的常量。说明const是为了防止程序员修改数据的修饰符,这样可以保证数据的安全。
指针和const
上面说到,在数组前面声明const,那么这个数组的元素值是不能改变的。那么const和指针一起使用又会怎么样呢?
int age=39;
const int *pt=&age;
这个程序使指针pt指向age,如果这样设定,那么*pt的值是一个const值。
*pt+=1 //不行,*pt不能改变,*pt的属性是const,
但是不是仅仅如此,将(*pt)设置为const,只是说明对于(*pt)来说,(pt)的值是一个const值,但是这并不意味着,原来的int类型的age变量也会因此不能修改。毕竟,归根结底,age还是int类型,并没有设置为const,所以从这一方面来看,*即不能通过(pt)来改变age的值,但是可以通过age来改变age的值(说的可能有点绕)
举个例子:小明有一本书,小华说借这本书品味一番。那么小华不可以把这本书撕下两页,或者涂涂改改,因为这不是小华的书,且小华说了仅仅是借走读一读。但是小明却可以随意处理这本书(撕掉,涂涂改改),因为这本来就是小明的书。
值得一提的是:有的人异想天开,既然const int 不能改变值,那么再设置一个int 类型指针指向const int的指针不就好了。这应该肯定是不行的,因为不能将const int的值给int *的值,下面程序看
#include<iostream>
using namespace std;
int main()
{
int age = 19;
const int* pt = &age;
//*pt += 1; //禁止
const int* tp = pt;
//*tp+=1; //禁止
int *tz=pt //禁止 ,pt是const int *类型,tz是int*型
cout <<"age="<< age << endl <<"*pt="<< * pt << endl <<"*tp="<< * tp << endl;
age += 1;//允许
cout << "age+1=" << age << endl;
return 0;
}
难点
上面提到的一级关系很好理解,但是如果将指针指向指针,进入两级间接关系时,与一级间接关系一样将const和非const混合的指针赋值方式将不再安全。为什么呢?
如果可以这样做那么会造成什么结果:
先看这个
#include<iostream>
using namespace std;
int main()
{
int** a;
int* b;
a = &b;
int n = 10;
*a = &n; //相当于b=&n
cout << "**a="<< **a << endl <<"*b="<< * b << endl << "n=" << n << endl;
*b = 20; //相当于n=20
cout << "**a=" << **a << endl << "*b=" << *b << endl << "n=" << n << endl;
return 0;
}
上面这个程序说明,可以通过*b来改变**a的值和n的值,
如果加i将n设置为const值,那么下面这个程序是不是可以通过p1改变n的值呢》、?
const **int pp2;
int *p1;
const int n=13;
pp2=&p1; //不允许这样做,否则会使n的值可以改变
*pp2=&n;
*p1=10;
结论是不会,因为如果可以使pp2指向p1,然后使*pp2指向n,也就是p1指向n,此时(*p1),(**pp2)的值就是n的值,但是由于(*P1)是int型的,所以说明(*p1)的值可以改变,一改变(*p1)的值,则n也会改变,这样就造成一个冲突,因为n是const int型的,n是不能改变的。所以,理所当然,为了保护n的完整,不能让pp2指向p1,也就是,不能让const (int **)类型指向int **类型.
说的很绕,以后还有机会细细说的。
如果声明数组是const类型,则不能像平常一样将数组名当作实参传递给函数形参。
const int month[12]={1,2,...};
int sum(int arr[])
{}
int j=sum(month); //错误,不能将const int *类型传递给int *,
//正确的方式是将调用函数的形参也声明const,int sum(const int arr[])
前面总结了const int *的用法,那么int *const p又有什么区别呢?
我的看法是:const左边的全部内容是一个整体!
即,const int *p: *p是不能改变的,但是p可以改变
const int *p;
int n=10;
p=&n;
int m=20;
p=&m; //可以,
*p=30 //不行
那么同理,int *const p; 可以改变 *p,不能改变p
int n=10;
int* const p=&n;
cout << "*p="<< * p << endl;
*p += 1;
cout << "*p=" << *p;
int m = 20;
//p = &m; 不行
return 0;
6.函数和结构
函数的返回值可以是结构体
下面看一个程序:
include<iostream>
using namespace std;
struct travel_time
{
int hours;
int mins;
}
const int Min_per_hr=60;
travel_time sum(travel_time t1,travel_time t2); //该函数的返回值是结构体类型
void show_time(travel_time t);
int main()
{
travel_time day1={5,45};
travel_time day2={4,55};
travel_time trip=sum{day1,day2);
cout<<"Two-day total: ";
show_time(trip);
travel_time day3={4,32};
cout<<"Three-day total:";
show_time(sum(trip,day3); //因为sum函数的返回值是travel_time类型,所以可以做实参
return 0;
}
travel_time sum(travel_time t1,travel_time t2)
{
travel_time total;
total.mins=(t1.mins+t2.mins)%Mins_per_hr;
total.hours=t1.hours+t2.hours+(t1.mins+t2.mins)/Mins_per_hr;
return total;
}
void show_time(travel_time t)
{
cout<<t.hours<<" hours,"<<t,mins<<"mins\n";
}
通过这个程序可以知道结构体变量也是可以做实参的,也可以是函数的返回值。实际上结构体变量和内置类型变量没有什么区别,用法都一样,包括指针的用法。
7.函数指针与数据项相比,函数也有地址,函数的地址就是存储其机器语言代码的内存的开始地址。通常,这些地址对用户而言并不重要。但对程序来说很重要。
1.函数的地址;
函数的地址就是函数名称。比如int max()函数的地址就是max
2.声明函数指针;
声明指向某种数据类型的指针时,必须指定指针指向的类型。那么,声明指针指向函数的指针时,也必须指定指向的函数类型。
//比如有一个函数
int pam(int); //函数类型为int,函数名为pam
//那么要用一个指针指向这个函数,这么做
int (*p)(int);//
p=pam//则p指向函数pam
通常,要声明指向特点类型的函数的指针,可以首先编写这种函数的原型,然后用(*p)来替换函数名,这样,p就是这类函数的指针。
3.使用指针来调用函数
#include<iostream>
using namespace std;
int max(int x, int y) //定义一个函数
{
if (x > y)
return x;
return y;
}
int main()
{
int (*p)(int, int); //声明一个函数指针
p = max; //指针指向函数
int c = (*p)(1, 2); //调用函数
cout << c;
return 0;
}
上面的程序可以发现,当指针指向函数的时候,实际上就和普通指针指向普通变量的用法一样了,使用指针来 *** 作函数,和直接使用函数是一样的。
C++允许像使用函数名一样使用指针,;
int c=p(1,2) //这样也可以,为什么可以?那就不清楚了
给一个函数指针的示例以便于理解:
#include<iostream>
using namespace std;
double betsy(int);
double pam(int);
void estimate(int lines, double (*pf)(int));
int main()
{
int code;
cout << "how many lines code do you want?";
cin >> code;
cout << "here is Betsy's estimate\n";
estimate(code, betsy); //传递函数名
cout << "here is pam's estimate\n";
estimate(code, pam); //传递函数名
return 0;
}
double betsy(int lns)
{
return 0.05 * lns;
}
double pam(int lns)
{
return 0.03 * lns + 0.0004 * lns * lns;
}
void estimate(int lines, double (*pf)(int))
{
cout << lines << " lines will take ";
cout << (*pf)(lines) << " hours\n";
}
通过该程序的体会是:函数名有点像数组名。。。
深入探讨函数指针
这路日后再补,有点费解,今天先到这里,要去备考了。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)