在平常写程序过程中,无非要使用到函数,写程序的时候经常不知道实参传上去的形参该对应什么类型,或者说有时候不知道怎么样传参,参数的类型该怎么一一对应,还有就是函数返回值的类型问题,博主通过大量失败经验,总结了以下内容
对于无参数的传递就比较简单了,实参放空,形参放空就可以了,具体看以下例子:
#include
void fun(); //这里是函数原型,不需要放参数
int main()
{
fun();//这里是函数调用,不需要放参数
}
void fun()//这里是函数定义,不需要放参数
{
printf("Hello World");
}
打印 Hello World
这里需要注意的是无参数的函数可以有返回值,而有参数的函数可以没有返回值,有没有返回值与函数是否有参数并无关系
传递数字或者字符到函数中,在传递的时候注意实参与形参类型要一一对应
例:
/* 示例2.1 */
#include
void fun(char s, int n, double m, unsigned long long k);
int main()
{
fun('A', 1 , 2.34 , 123456789123);/* 123456789123超过了INT_MAX,
如果函数体里面仍然用%d输出则会出现 int类型的数据上溢 */
}
void fun(char s, int n, double m, unsigned long long k)
{
printf("%c\n", s);
printf("%d\n", n);
printf("%f\n", m);
printf("%llu\n", k);
}
每种数据都要对应相应的数据类型,如这里 ’A’ 是字符串类型,函数原型以及函数定义里面都要用 char 类型接收,如果这里用 int 类型接收,则传递的是 ’A’ 字符对应的 ASCII 值,如:
/* 示例2.2 */
#include
void fun(int s);
int main()
{
fun('A');
}
void fun(int s)
{
printf("%d,%c", s , s );
}
则打印 65,A
在示例2.2中,如果函数调用里面传递的是 'ABCD’这个字符常量(注意,这里不是字符串)会怎么样?我们来试一试
#include
void fun(int s);
int main()
{
fun('ABCD');
}
void fun(int s)
{
printf("%d,%c", s, s);
}
结果打印:1094861636,D
并没有报错,以 %d 打印的却是这么一大串奇怪的数字
其实这里打印的数字并不是随机打印的,而是一个确切的值,因为 C 语言将字符常量视为int类型而非 char 类型,在计算机中的一切都是以二进制来传递的
‘A’ 的二进制:0100 0001
‘B’ 的二进制:0100 0010
‘C’ 的二进制:0100 0011
‘D’ 的二进制:0100 0100
所以这里传递给s的是这四个八位二进制的组合
0100 0001 0100 0010 0100 0011 0100 0100
共32位,所以打印出来的就是这个二进制数代表的一个int类型的值,即1094861636
那为什么这里以%c形式却只打印 ‘D’ 这个字符?
因为这里有 32 位二进制,而 char 类型只占 8 位,所以只有后八位二进制有效,即 ‘D’ 字母的二进制
当然这里如果你要传递字符常量 ‘ADBCE’ ,肯定是不行的,因为每个字符占 8 位,而 int 只有 32 位,所以传递五个就会报错
在示例2.1中,函数调用的实参 1 是 int 类型,所以在函数原型和函数调用都要用 int 类型的形式参数来接受传来的 1 ,2.34 与 123456789123 需要用相同类型的形式参数来接收
但如果传入的类型与接受的类型不一致,就会发生强制类型转换,比如:
#include
void fun(double n , int m , int k , double p);
int main()
{
fun(1,2.34, 123456789123, 12345678912345678912);
}
void fun(double n , int m , int k , double p)
{
printf("%f\n",n);
printf("%d\n",m);
printf("%d\n",k);
printf("%f\n",p);
}
则打印
1.000000
2
-1097262461
12345678912345679872.000000
可以发现传入的 1 变成了由 int 强制转换成 double 类型,而传入的 2.34 则由 double 类型转为 int 类型,对于 123456789123 由 unsigned long long 类型转变为 int 类型发生了数据溢出,而第四个强制转变为 double 类型时,由于 double 的精度限制,则打印不准确
如果形式参数是 char 类型,则自动将由实参传入的值强制转换成 char 类型(也要注意有数据溢出),函数再把转换成的值当成 ASCII 码值处理
强制转换类型如下
需要注意的是由右边强制转换成左边可能会出现精度丢失
同时,在给函数传递参数时, C 编译器会把 short 类型的值自动转换成 int 类型的值,因为 int 类型被认为是计算机处理整数类型时最高效的类型。因此,在 short 和 int 类型的大小不同的计算机中,用 int 类型的参数传递速度更快!
普通指针:
↓↓↓↓↓↓↓↓↓
#include
void fun(int *p);//把*p换成p[]也是可以的
int main()
{
int n = 3;
int* p = &n;
fun(p);
}
void fun(int *p)//把*p换成p[]也是可以的
{
//其他代码已省略
}
结构体指针
↓↓↓↓↓↓↓↓↓↓
#include
struct stu_inf //student_information的缩写
{
char name[10];
char gender[5];
int ID;
};
void fun(stu_inf *temp);//传递类型是结构指针
int main()
{
struct stu_inf student =
{
"LiHua",
"Boy",
12345
};
fun(&student);//把结构体的地址传给函数,和数组不同,结构变量名不是地址的别名
}
void fun(stu_inf *temp) //传递类型是结构指针
{
//其他代码已省略
}
对于一个数组 array[ ]={1,2,3,4,5},和一个整数 n=5 ,如果要把数组传给一个函数,然后对数组进行一系列操作,实参传递应该传递数组名,因为数组名就是地址,可以称作数组地址,也可以看成第一个元素的地址(需要注意的是数组名代表数组的首地址,是常量,不能改变,所以某种程度上等价于指针,但不是指针变量),由于函数原型可以省略参数名,所以下面四种原型都是等价的:
void fun(int *arr,int n); //这里的变量名是假名,不必与函数定义的形式参数名一致
void fun(int * ,int );
void fun(int arr[ ],int n);
void fun(int [ ] ,int );
但是,在函数定义中不能省略参数名,下面两种形式的函数定义等价:
void fun(int *arr,int n)
{
//其他代码省略
}
void fun(int arr[ ],int n)
{
//其他代码省略
}
同理,传递字符数组也是一样的
强调一下,函数调用的时候传递是数组名,它是一个地址,但是在作为参数传递的时候数组名会退化成一个指针,也就是说这里的 arr 不是数组名,而是一个指向 array 数组首元素的指针,所以要是想在函数定义里面使用 sizeof(arr) / sizeof(arr[0]) 来计算数组的长度就不可行了,因为 64位 电脑下任何指针的大小固定为 8 字节( 32位 下为 4 字节),而原数组大小不一定为 8 字节。
传递二维数组时声明函数的形参要比传递一维数组麻烦一点,通常思路是将二维数组名传递给二级指针(我第一次就是这么干的),我们来试一下:
#include
void fun(int **arr);
int main()
{
int array[2][3] = { {1,2,3} ,{4,5,6} };
fun(array);
}
void fun(int** arr)
{
printf("%d",arr[0][0]);
}
运行报" int (*)[3] "类型的实参与 " int ** " 类型的形参不兼容的错误
二维数组名它是指向数组首地址的指针,这里说的是一个一维数组,而这一维数组里面的每一个元素又都指向了一个一维数组,所以二维数组实际上是数组的数组,例如这里的 array[0] ,它指向了一维数组{1,2,3},array [1] 指向了一维数组 {4 , 5 , 6} ,通俗的讲 array 是一个内含 2 个数组元素的数组,每个元素又包含 3 个 int 类型值的数组。而这里的 arr 它是一个指向指针的指针,一个指向指针的指针和一个指向数组的指针不兼容,也就是类型不同,指针之间的赋值比数值类型之间的赋值要严格。
所以这里我们需要的函数原型声明是:
void fun(int (*arr)[3]);
或者
void fun(int arr[][4]);
即如下
#include
void fun(int(*arr)[3]); //(*arr)[3]换成arr[][3]也可以
int main()
{
int array[2][3] = { {1,2,3} ,{4,5,6} };
fun(array);
}
void fun(int (*arr)[3]) //(*arr)[3]换成arr[][3]也可以
{
printf("%d", arr[0][0]);
}
同理在函数里面也不能用 sizeof 计算二维数组的行数与列数
拓展一下,int (*arr)[3], 含义是 arr 指向一个含有 3 个 int 类型值的数组
而 int *arr[3] 含义是 arr 是一个内含 3 个指针元素的数组,每个元素都是指向int的指针
因为 [ ] 优先级比 * 高
(更高维数组就是知识盲区了)
对于一个字符串 “ABC” 将其传入函数,如下:
#include
void fun(const char *str);
int main()
{
const char* str = "ABC";
fun(str);
}
void fun(const char *str)
{
//其他代码已省略
}
注意这里的字符串是一个常量,不能对其进行更改,但此时可以在函数里面用 strlen() 函数来计算字符串的长度
这里是否加 const 主要还是看软件吧,对于一些比较严格的比如 VS ,必须要加,否则会报错,而对于 Dev C++ 不加 const 只会给出警告
对于字符串数组:{ “Hello” , “Worlds” , “!” }
有以下代码:
#include
void fun(const char *str[]);//也可以用**str
int main()
{
const char* str[3] = { "Hello","Worlds","!" };
fun(str);
}
void fun(const char* str[])//也可以用**str
{
//其他代码已省略
}
这里可以用 *str[ ] 来接受参数,同时我们也可以用 **str 来接受参数,读到这里有可能会有疑惑,为什么上面讲到的二维数组不能用二级指针来接受参数,为什么这里可以用呢,理由如下:
上面讲到二级指针是指向指针的指针,而这里的str是一个含有三个指针元素的数组,而每个指针又指向了字符串,而字符串它实际也是一个地址(可以理解为一个指针),这个地址是字符串中第一个字符的地址,简单一点,用双括号括起来的内容就可以看作指向该字符串储存位置的指针。
注意,这里的字符串数组里面的字符串也是不能修改的,因为他们都是常量,如果要改变字符串请使用二维字符数组。如果要计算某个字符串的长度,可以用 strlen
#include
struct stu_inf //student_information的缩写
{
char name[10];
char gender[5];
int ID;
};
void fun(stu_inf temp);
int main()
{
struct stu_inf student =
{
"LiHua",
"Boy",
12345
};
fun(student);
}
void fun(stu_inf temp)
{
//其他代码已省略
}
这里调用 fun 函数时,编译器根据 stu_inf 的模板创建了一个名为 temp 的自动结构变量。然后,该结构的各成员被初始化为 student 结构变量相应成员的副本,所以在函数内部使用的时原来结构的副本进行计算,并不会对原结构成员有任何影响。
#include
#include
struct stu_inf //student_information的缩写
{
char name[10];
char gender[5];
int ID;
};
void fun(stu_inf *temp);//这里也可以用temp[]
int main()
{
struct stu_inf student[5];//结构体数组
fun(student);
}
void fun(stu_inf *temp) //这里也可以用temp[]
{
strcpy(temp[0].name, "LiHua");
printf("%s", temp[0].name);
}
这里的 student 本身不是结构名,他是一个数组名,该数组有五个元素,每个元素都是 struct stu_inf 类型的结构变量,而 temp 是一个指向该数组的指针,其实就是数组的传递而已,但这里要与前面提到的传递结构要区分开来。
需要注意的时如果在函数里面需要对结构体里面的 char 数组变量赋值,例如要对 temp[0] 里面的 name 赋值,不可以直接用 temp[0].name=”LiHua” ,还是老问题,这里的 name 是一个数组名,它是一个固定的地址,而字符串 ”LiHua” 也是一个地址,两者地址不一样,所以不能直接这么用,可以使用strcpy函数,也可以使用其他的输入函数,如 scanf,fgets 等等
#include
void show(void(*fp)(int arr[]),int array[]);
void fp(int arr[]);
int main()
{
int array[] = { 1,2,3 };
show(fp, array);//调用show函数
}
void fp(int arr[]) //fp函数定义,传入array数组
{
//其他代码已省略
}
void show(void(*fp)(int arr[]),int array[]) //传入fp函数以及array数组
{
(*fp)(array);//调用fp函数,把fp函数作用于array数组
}
void show(void(*fp)(int arr[]),int array[]);
这句话里声明了两个形参fp和array,
fp是一个函数指针,array是一个数组指针,
fp指向的函数接收arr[]类型的参数,返回类型为void(就是不返回),array指向一个数组;
我们可以根据自己的选择来选定的函数进行使用
介绍完实参与形参类型的一一对应,下面以fun()函数为例来介绍下的返回值对应类型
函数原型与函数定义应为
void fun();//函数原型,参数自拟,下同
void fun() //函数定义
{
//其他代码已省略
}
#include
char fun();
int main()
{
printf("%c", fun());
}
char fun()
{
return 'a';
}
#include
int fun();
int main()
{
printf("%d", fun());
}
int fun()
{
return 123;
}
#include
double fun();
int main()
{
printf("%f", fun());
}
double fun()
{
return 12.34;
}
同理,如果函数的返回值类型与函数声明的类型不匹配会发生什么情况?
实际上主调函数得到的返回值相当于把函数中指定的返回值赋给与函数类型相同的变量所得到的值,说起来有点拗口,其实就是强制类型转换。
#include
char* p;//这里需要定义一个全局变量
char *fun();
int main()
{
char* p1;
p1 = fun();
printf("%c",*p1);
}
char* fun()
{
char str = 'a';
p = &str;//如果在函数内部定义p的话不安全
return p;
}
同时,该结构也可以返回一个指向字符数组的指针
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
#include
char *fun();
int main()
{
char* p1;
p1 = fun();
printf("%c",p1[2]);
}
char* fun()
{
char str[] = {'A','B','C'};
return str;
}
最后结果打印 C
返回指向一个 int 或者 double 值的指针,以及返回指向 int 数组或 double 数组的指针都大同小异
如果 二维数组 的空间 是在调用函数内申请的,只能使用 new 或者 malloc 的形式分配的空间,才能返回,返回的时候返回空间的首地址就行了。调用函数内 这种形式 声明的数组 int array[4][4] ,分配在栈里,不能返回
例如,在函数定义里面这样操作
{
int arr[2][3]= { {1,2,3} , {4,5,6} };
return arr;
}
是错误的,如果用 C 语言实现,但如果申请空间的话是可以的
如下(重点在注释部分)
#include
#include
int**fun();
int main()
{
int** p;
p = fun();
//其他代码已省略
for (int i = 0; i < 3; i++)
free(p[i]);
free(p)
//malloc在堆区,函数中的变量在栈区,必须手动释放
}
int** fun()
{
int** arr;//定义一个二级指针
arr= (int**)malloc(3*sizeof(int*));
for (int i = 0; i < 3; i++)
arr[i] = (int*)malloc(4 * sizeof(int));
// ↑↑↑ 动态分配内存,申请了一个3×4的二维数组的空间
//其他代码已省略
return arr;
}
需要注意这几方面: C 语言动态申请的内存需要手动释放,而且动态二维数组的申请释放比较特殊,如果你的二维数组不大,可以直接定义成全局变量。
#include
#include
struct stu_inf //student_information的缩写
{
char name[10];
char gender[5];
int ID;
};
stu_inf * fun();
int main()
{
struct stu_inf* p;
p = fun();
//其他代码已省略
}
stu_inf* fun()
{
struct stu_inf* student=(stu_inf *)malloc(sizeof(stu_inf));//指针要申请内存
return student;
}
#include
#include
struct stu_inf //student_information的缩写
{
char name[10];
char gender[5];
int ID;
};
stu_inf fun();
int main()
{
struct stu_inf p;
p = fun();
printf("%s", p.name);
//其他代码已省略
}
stu_inf fun()
{
struct stu_inf student =
{
"LiHua",
"Girl",
123
};
return student;
}
这篇博客是自己平时写程序积累的经验,主要参考了《C Primer Plus》这本书,写这篇博客的过程中也收获了很多新知识,看来要想掌握牢固知识的一个较佳的方法就是把它写下来,能给别人讲明白自己肯定就明白了
最后,欢迎各位朋友指出本篇文章的不足以及可能出现的错误