C++函数的基本用法(一)

本文深入探讨函数的定义、原型、调用及参数传递机制,解析数组、字符串、结构体、对象作为函数参数的使用技巧,详述函数指针的概念与实践案例,适合进阶学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

主要内容:
基本语法:函数原型、函数定义、函数调用
理解函数参数传递工作机制:值传递(基本类型变量、结构、类对象)、指针传递(数组)
c风格字符串作为函数参数、返回值时基本用法,一些常用类对象(array string)在函数中做参数、返回值基本用法
函数指针

正确使用函数三大步骤:
函数定义、函数原型、函数调用
1、函数定义
以有无返回值来分,可分为无返回值、有返回值的函数:对于有返回值的函数来说,返回的值返回给调用函数,注意数组不能作为返回类型而出现,但数组可作为结构、类型的组成部分出现。所返回的数值可用作赋值、显示等用途。
被调用函数和调用函数如何就返回一个数值达成一致? (返回数值机制)
被调用函数将返回的值存储在寄存器或是内存中,调用程序到相应的寄存器或是内存中查找,函数原型告知调用程序所返回的数据类型(找到内存或方便取值出来),函数定义描述函数所返回的数据类型。如下图所示:
在这里插入图片描述
函数中包含多条返回语句,遇到第一个return 被调用函数停止执行,将值返回至调用程序。

2、函数原型
1.为什么需要函数原型?
提供了函数到编译器的接口,若没有原型这一概念的支撑,编译main时,需要先向前查找得到函数定义来得知函数返回类型和参数类型进而分配合适的内存空间,效率低下;当程序存在于多个文件时,main函数可能对某一文件没有访问权限;执行函数时,从main开始,我们通常也是按此逻辑来编写程序。
常存在于头文件中。
2.基本语法
参数类型要写明,可以不写变量名(变量名也可与定义时声明的不一样)
3.函数原型的功能
可保证:
编译器正确处理函数返回值(根据原型中声明的类型从寄存器或是内存取出数据)
编译器检查参数数目、类型是否正确(做出部分类型转化)

3、按值传参(函数参数是基本类型):
形参(参数)、实参(参量),将参量赋值给参数(等同于副本)。
函数内部变量和参数几乎是自动变量(除去new分配的内存空间),他们随着函数调用而被分配空间、函数终止而释放空间。
不同函数可以出现多个同名的变量,这些变量不是长久的(任何时刻只有一个函数得到调用,被调用的函数有一个活动栈),就算与全局变量相同,局部变量覆盖全局变量。(main中和被调用函数均有一个同名的变量,被调用函数怎么知道使用自身定义的同名变量而不是main函数中定义变量????疑问尚未解决)

#include "stdafx.h"
//多次显示一个字符
#include<iostream>
using namespace std;
void display(int n, char c);
int main()
{
	cout << "input q to quit ,else input character and times" << endl;
	char c;
	cin >> c;//ch=cin.get()或是cin.get(ch) 获取到输入流中的空格  ; cin>>c跳过空格
	int n;
	
	while (c != 'q')
	{
		cin >> n;
		display(n, c);
		cout <<" "<< n;		
		cout << "\ninput q to quit ,else input character and times" << endl;
		cin >> c;

	}
	//cin.get();
	//cin.get();
    return 0;
}

void display(int n,char c)
{
	while (n-- > 0)
	{
		cout << c;
	}

}
#include "stdafx.h"
#include<iostream>
using namespace std;
long double probability(unsigned , unsigned);
//计算阶乘时,不是直接先算出所有分子分母的乘积,有可能数值太大了而溢出,而是计算分子分母的中间值!
int main()
{
	unsigned counts;
	unsigned total;
	while ((cin >> counts >> total) && counts <= total)
	{
		if (char(counts) == 'q')
			break;
		cout<<probability(counts, total)<<endl;
		cout << "input counts and total ,or q to quit" << endl;

	}
    return 0;
    
}

long double probability(unsigned counts,unsigned total)
{
	long double result = 1.0;
	for (; counts > 0; counts--, total--)
	{
		result = result*total / counts;
	}
	return result;
}

4、函数参数是数组类型(按值传递,此值为地址,与基本类型赋值数据值相比,若是赋值数组的内容,所付出的时间空间开销太大):
处理一个数组,我们只需要第一个元素的地址和元素的个数,便可以直接操作原数组;针对这一点,通过改变参量中的第一个元素的地址或者元素个数就可以“伪造”新的数组了。
定义如下: typename functionname(typename [] 变量名 ,int n)
第一个参数看似是一个数组,实则为指针,代表数组中第一个元素的地址
或者写为:typename functionname(typename *指针名 ,int n)
通过程序测试,分别对参数数组名、参量数组名取sizeof,可以看出实参得到的是整个数组所占的字节数;而形参中得到的是指针变量所占用的空间。因此,可以看出参数仅仅传递数组指针,被调用函数无法得知数组的长度。除了用上面的方法,显示指定数组长度外,可以使用两个指针表达数组,形如:
typename functionname(typename *指针名 ,typename *指针名)
第一个参数是数组第一个元素的地址,第二个元素是数组末尾元素后一个位置的地址值,这样表达在编写函数界线判定时很有帮助。

//计算数组所有、部分元素的和

#include "stdafx.h"
#include<iostream>
using namespace std;
double total(double *p, int n);
const int SIZE = 7;
int main()
{
	double a[SIZE] = { 1.2,3.3,2.3,2,1,4,6 };
	cout << sizeof(a) << endl;//整个数组所占字节个数
	cout << total(a, SIZE) << endl;
	cout << total(a + 2, 3) << endl;
	cin.get();

    return 0;
}

double total(double *p, int n)
{
	cout << sizeof(p) << endl;
	int i;
	double sum = 0;
	for (i = 0; i < n; i++)
		sum += *(p + i);
		//sum += p[i];

	return sum;
}

通过上面我们已经知道如何在函数中传递数组,现在把OOP的思想加到数组上,来为数组定义一系列的操作!
传递一个空数组,为数组赋值:
函数参数是指针,可直接对原数组赋值
显示数组:
如果此函数的目标不在于改动数组,那么为了更好的达成目标,在传递参数的时候,用const来修饰数组。
修改数组值,不可以再用const来修饰数组。
const与 指针的关系:(当指针指向基本类型时,能用const修饰就用const修饰,而指针指向的是地址时,不用const修饰指针)
const 类型 *p :执行常量的指针,执行的数据可以不是const的,这里主要是防止使用指针p修改执行的内容,对于数组来说可保证不破坏原始数组;
类型*
const p:常量指针,不能再修改指针值
对于数据值本身是const的变量来说,其指针一定要用const修饰;数据值本身不是const的变量,可用指向const变量的指针,也可用普通指针

#include "stdafx.h"
#include<iostream>
using namespace std;
const int SIZE = 5;
int  fill_array(double *a ,int n);//n代表数组的最大长度 返回实际填充数组的元素个数
void display(const double *begin, double *end);//不修改元素值,使用const修饰数组
void revalue(double *begin, double *end,double factor);//修改数组元素值


int main()
{
	double a[SIZE];
	int len=fill_array(a, SIZE);
	cout << len << endl;
	display(a, a + len);
	revalue(a, a + len, 2.1);
	display(a, a + len);
	cin.get();
	cin.get();
	//cin.get();
    return 0;
}

int  fill_array(double *a, int n)//输入负数停止输入,可以识别类型不一致的错误输入
{
	int i=0;
	double tmp;
	for (; i < n; i++)
	{
		cin >> tmp;
		if (!tmp)
		{
			cin.clear();
			while (cin.get() != '\n')//删除缓冲流中的所有剩余字符(错误指类型不符),结束输入
				continue;
			cout << "输入类型错误" << endl;
			break;
		}
		else if (tmp < 0)
			break;
		else
			a[i] = tmp;

	}
	return i;//i++得到执行,正好表明数组长度
}

void display(const double *begin, double *end)//不修改元素值,使用const修饰数组
{
	const double *p = begin;
	for (; p < end; p++)
	{
		cout << *p << " ";
	}
	cout << endl;
}




void revalue(double *begin, double *end, double factor)//修改数组元素值
{
	double *p;
	for (p = begin; p < end; p++)
	{
		*p = *p*factor;
	}
}

程序测试有点问题,输入类型不是double时,无法得到前面正确输入的数组,尚未解决

5、二维数组作为参数传给函数
函数定义:
返回类型 functionname (typename 数组名[][列元素个数],行元素的个数)
或者是:
返回类型 functionname (typename (*指针名)[列元素个数],行元素的个数):指向数组的指针
在函数中,将指针视为数组名来使用,有两次解引用操作

6、C风格字符串与函数
情况一:参数是字符串
回顾一下,C风格字符串是什么?
由一系列字符组成且最后一个字符是空字符。将字符串理解为字符数组+末尾为空字符!既然是数组,就可以用上面的知识了。但由于包含隐性条件(最后一个字符为空字符)所以不需要传递元素个数。

表示字符串三种方式(c风格字符串):
1.指针:字符串首个字符的地址
2.char[]
3.字符串字面值
但归根到底都可以视为指向字符的指针。

//c风格字符串 计算给定字符出现的次数
#include<iostream>
using namespace std;

int c_count(char * p,char c);

int main()
{
	char a []= "abdedkk";
	char *b = "ddkke";
	cout<<c_count(a, 'd')<<endl;
	cout << c_count(b, 'k')<<endl;
	cin.get();
    return 0;
}

int c_count(char *p, char c)
{
	int count = 0;
	while (*p)
	{
		if (*p == c)
			count++;
		p++;
	}
	return count;
}

情况二:返回值是字符串
返回指向字符的指针

//函数生成一个字符串并返回其地址值

#include<iostream>
using namespace std;

char * gener_string(int times, char c);
int main()
{
	int times;
	char c;
	cin >> times;
	cin >> c;
	char *string=gener_string(times,c);
	cout << string;
	delete[] string;
	
	cin >> times;
	cin >> c;
	string = gener_string(times, c);
	cout << string;
	delete[] string;
	char t=cin.get();
	cout << t << endl;
	cin.get();
    return 0;
}

char * gener_string(int times, char c)
{
	char * tmp = new char[times + 1];//为了存储末尾空字符值
	tmp[times] = '\0';//小技巧,从末尾开始构造字符串
	while (times-- > 0)
		tmp[times] = c;
	return tmp;
}


7、函数与结构
结构作为参数传递的三种形式:
1.结构变量(值传递)

下面三个例子都用到了当输入不是数值类型时终止循环。

#include<iostream>
using namespace std;

struct time
{
	int hours;
	int mins;
};
time add(time, time);
void show(time);
const int Min = 60;

int main()
{
	time t1;
	time t2;
	time result;
	while (cin >> t1.hours >> t1.mins)//h和m是数值
	{
		cin >> t2.hours >> t2.mins;
		result = add(t1, t2);
		show(result);
	}
    return 0;
}

time add(time t1, time t2)
{
	time t;
	t.hours = t1.hours + t2.hours + (t1.mins + t2.mins) / 60;
	t.mins= (t1.mins + t2.mins) % 60;
	return t;
}
void show(time t)
{
	cout << t.hours << ":" << t.mins << endl;
	
}
#include<iostream>
#include<cmath>
using namespace std;

struct Polar
{
	double radius;
	double angle;
};
struct Rect
{
	double x;
	double y;
};
void show_polar(Polar tmp);
Polar rect_to_polar(Rect tmp);

const double Ang = 57;
int main()
{
	Rect rect;
	Polar p;
	while (cin >> rect.x >> rect.y)
	{
		p = rect_to_polar(rect);
		show_polar(p);
		cout << "if you want to quit ,input q" << endl;
	}
    return 0;
}

void show_polar(Polar tmp)
{
	cout << tmp.angle*Ang << "度 " << tmp.radius << endl;

}
Polar rect_to_polar(Rect tmp)
{
	Polar t;
	t.radius=sqrt(tmp.x*tmp.x + tmp.y*tmp.y);
	t.angle=atan2(tmp.y, tmp.x);
	return t;
}

2.指针



#include<iostream>
#include<cmath>
using namespace std;

struct Polar
{
	double radius;
	double angle;
};
struct Rect
{
	double x;
	double y;
};
void show_polar(Polar *tmp);
void  rect_to_polar(Rect *tmp, Polar *t);

const double Ang = 57;
int main()
{
	Rect rect;
	Polar p;
	while (cin >> rect.x >> rect.y)
	{
		rect_to_polar(&rect,&p);
		show_polar(&p);
		cout << "if you want to quit ,input q" << endl;
	}
    return 0;
}

void show_polar(Polar *tmp)
{
	cout << tmp->angle*Ang << "度 " << tmp->radius << endl;

}
void  rect_to_polar(Rect *tmp,Polar *t)//犯了一个错误:返回一个指向结构体的指针,定义了指针后,仅仅给指针分配了空间,但在没有分配结构体空间下,直接给成员变量赋值。。。。连空间都没有分配,赋哪门子的值。。。。
{
	
	t->radius=sqrt(tmp->x*tmp->x + tmp->y*tmp->y);
	t->angle=atan2(tmp->y, tmp->x);
	
}

虽然吧,结构包含多个数据值,但作为参数时,与数组不同,数组名实际是首个元素的地址,结构名却不表示地址。传递结构变量时就是值传递,生成新的结构变量副本,因此当结构变量很大时,效率不高。

8、函数与对象
这里举了几个标准类对象,string对象、array对象在函数中的简单用法。
上面讲了结构与函数,类是结构上的衍生,因此与结构在函数上的用法一致,可以值传递、引用、指针。
第一个例子是关于string对象的简单用法:

//从键盘输入字符串存入到字符串数组中并显示

#include "stdafx.h"

#include<iostream>
#include<string>
using namespace std;
const int SIZE = 5;
void show(string a[],int len);//昨天刚学的数组的使用方法,今天就忘记了,数组实际上是指针,怎么能把数组的长度写入到[]内呢??

int main()
{
	string a[SIZE];
	for (int i = 0; i < SIZE; i++)
		getline(cin, a[i]);
	show(a, SIZE);
	cin.get();
    return 0;
}

void show(string a[], int len)
{
	for (int i = 0; i < len; i++)
		cout << a[i] << endl;

}

第二个例子使用了array对象:

//使用array对象存储开支,使用array对象作为参数与使用数组作为参数差别,使用数组有两个参数,一个表示指针,一个表示长度;而使用array对象,第一个参数直接指明了类型和长度

#include "stdafx.h"
#include<iostream>
#include<array>
#include<string>
using namespace std;
const int SIZE = 4;
const array<string, SIZE> name = {"spring","summer","autumn","winter"};

void fill(array<double,SIZE> * p);
void show(array<double, SIZE>);

int main()
{
	array<double, SIZE> expense;
	fill(&expense);
	show(expense);
	cin.get();
	cin.get();
    return 0;
}


void fill(array<double, SIZE> * p)
{
	for (int i = 0; i < SIZE; i++)
	{
		cout << name[i] << ":";
		cin >> (*p)[i];
	}
		
}
void show(array<double, SIZE> a)
{
	for (int i = 0; i < SIZE; i++)
	{
		cout << name[i] << ":" << a[i] << endl;
	}
}

9、函数指针
可以通过函数指针调用函数,函数指针可以指向相同返回类型、相同参数类型、相同参数个数的多个函数,灵活性更好。
函数地址:机器代码在内存中的起始位置
想象这样一种应用场景:函数指针作为函数参数,便可以使用同一个函数在不同的时刻调用不同的函数。
为此,我们需要突破三点:
1.函数地址如何表达? 函数名就是函数地址,不要加参数
2.函数指针如何表达?
使用指针,程序猿一定要指明指针所指向数据类型
对于函数,类似于原型声明,需要指出参数类型和返回类型;此外,为了表明是指针类型,要加*,而且*的优先级使得不加括号,会成为函数返回值为指向某一类型的指针。
在这里插入图片描述
有了1和2,我们可以定义函数指针并为其初始化,使其指向某个函数:
在这里插入图片描述

3.用函数指针来调用函数
(*p)(参数)或是p()
在这里插入图片描述

综上以上三点,可得:

//不同算法有不同的复杂度,编写一个函数,可以就算不同行数、不同的算法所需时间。

#include "stdafx.h"
#include<iostream>

using namespace std;

double estimate(int line, double(*fp) (int));
double a(int lines);
double b(int lines);

int main()
{
	cout<<estimate(30, a)<<endl;
	cout<<estimate(30, b);
	cin.get();
    return 0;
}

double a(int lines)
{
	return lines*0.3;
}
double b(int lines)
{
	return lines*0.03;
}

double estimate(int line, double(*fp) (int))
{
	return (*fp)(line);
}

深入探究函数指针:
在这里插入图片描述

以上三个函数可用一个函数指针来表示:
在这里插入图片描述
利用auto推断类型,但是也有局限,仅仅适用于单值初始化,而不能用于列表初始化
在这里插入图片描述
调用函数指针所指向的函数

在这里插入图片描述

如何表示函数指针数组?
想表达数组需要确定数组大小,如下所示,数组大小为3,[]优先级更高代表数组元素为指针,具体指针指向什么,通过其他信息来确定。
在这里插入图片描述
如何使用函数指针数组中的元素调用函数?
在这里插入图片描述
上面已经讲了函数指针数组,现在定义指向函数指针数组的指针,多一级指针,多一个*,先是指向一个数组,数组元素是函数指针,是指针就要加*,就有了下面的表达,注意加括号。
在这里插入图片描述
仅仅有指向数组的指针(数组元素是函数指针),如何利用这些信息调用函数?取出数组元素(即是函数地址直接调用)
(*pd)[i](参数)

关于函数指针、函数指针数组、指向函数指针数组的指针综合使用例子如下,这里用了直接定义类型的方法,但可以用auto根据赋值的值来判断数据类型,能用auto就用auto!

//函数指针用法

#include "stdafx.h"
#include<iostream>
using namespace std;

double * f1(double a[], int len);
double *f2(double *a, int len);
const int SIZE = 3;
int main()
{
	//定义函数指针并做初始化
	double * (*fp)(double *, int) = f1;
	auto fp1 = fp;//auot根据初始化来类型推断
	double three[SIZE] = {1,2,3};
	//利用函数指针调用函数
	cout<<(*fp)(three, SIZE)<< "value is:"<<*(*fp)(three, SIZE)<<endl;
	cout << (*fp1)(three, SIZE) << "value is:" << *(*fp1)(three, SIZE)<<endl;

	//定义二个元素的数组,元素是函数指针
	double *(*fp2[2])(double *, int) = {f1,f2};
	auto fp3 = fp2;//fp3是第一个元素的地址
	//取数组元素调用函数
	cout << fp2[0](three, SIZE) << "value is:" << *fp2[0](three, SIZE)<<endl;


	//定义一个指向函数指针数组的指针
	double *(*(*fp4)[2])(double *,int ) = &fp2;
	cout << (*fp4)[0](three, SIZE) << "value is " << *(*fp4)[0](three, SIZE)<<endl;


	cin.get();
    return 0;
}

double * f1(double a[], int len)
{
	return a;
}
double *f2(double *a, int len)
{
	return a + 1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值