C++primeplus p167-p208

1.简单介绍库函数

首先,什么是库函数?
库函数就是已经定义好的函数的一个集合,也就是说这个文件里面有很多函数的原型,你只需要通过函数名和参数来调用它们即可。

而库函数就在头文件所在的位置,比如<stdio.h>,那么就是说有一个文件名的名称是stdio.h,这个文件里面有很多程序员早先写好的函数,以方便我们编写程序的时候使用。那么如果想要使用这些函数,就必须声明使用这个库。比如想要使用scanf,printf函数,就要在使用的文件顶端写上#include < cstdio>

2.函数返回值

函数根据返回值可以分为两种函数:

  1. 有返回值的函数
  2. 没有返回值的函数

有返回值的函数

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.const

void 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";
}

通过该程序的体会是:函数名有点像数组名。。。

深入探讨函数指针
这路日后再补,有点费解,今天先到这里,要去备考了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值