C程序是由函数组成的,我们写的代码都是由主函数main(开始执行的。函数是C程序的基本模块,是用于完成特定任务的程序代码单元。
从函数定义的角度看,函数可分为系统函数和用户定义函数两种:
- 系统函数,即库函数:这是由编译系统提供的,用户不必自己定义这些函数,可以直接使用它们,如我们常用的打印函数printf()。
- 用户定义函数:用以解决用户的专门需要。
- 函数可以让程序更加
模块化
,从而有利于程序的阅读
、修改
和完善
假如我们编写一个实现以下功能的程序:
读入一行数字;对数字进行排序;找到它们的平均值;打印出一个柱状图。如果我们把这些操作直接写在main()
里,这样可能会给用户感觉代码会有点凌乱。假如我们使用函数,这样可以让程序更加清晰
、模块化
。
- 形参:变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量。
- 实参:可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使参数获得确定值。
#include<stdio.h> // 函数定义 返回值类型 函数名称(参数列表){ 函数体 return 返回值 } // 【形参】:add()中的a,b int add(int a, int b){ return a+b; } // 【实参】:add()中的1,2 int main(){ int t = add(1, 2); printf("%d", t); return 0; }
理论上是可以随意起名字,最好起的名字
见名知意
。应该让用户看到这个函数名字就知道这个函数的功能
。
【注意】:函数名的后面有个圆换号()
,代表这个为函数,不是普通的变量名。
在定义函数时指定的形参,在未出现函数调用时,它们并不占内存中的存储单元,因此称它们是
形式参数或虚拟参数
,简称形参
,表示它们并不是实际存在的数据。所以,形参里的变量不能赋值。
形参与实参:
- 形参出现在函数定义中,在整个函数体内都可以使用,
离开该函数则不能使用
。- 实参出现在主调函数中,进入被调函数后,
实参也不能使用
。- 实参变量对形参变量的数据传递是“值传递”,即
单向传递
,只由实参传给形参,而不能由形参传回来给实参
。- 在调用函数时,编译系统临时给形参分配存储单元。调用结束后,形参单元被释放。
- 实参单元与形参单元是不同的单元。调用结束后,形参单元被释放,函数调用结束返回主调函数后则不能再使用该形参变量。实参单元仍保留并维持原值。
因此,在执行一个被调用函数时,形参的值如果发生改变,并不会改变主调函数中实参的值。
若函数写再
main()
函数后面,则需要声明。
声明:extern 函数类型 函数名(参数列表)
在同一个文件夹中函数都是
全局变量
。
作用:
- 声明函数、变量。
- 系统库的调用。
使用:
在头文件
对应
的xx.c
中实现xx.h
声明的函数。
#include "xxx.h" int main(){ return 0; }
防止头文件重复包含
为了避免同一个文件被多次include,有俩种方式:
- 可在
xx.h
开头添加#pragma once
。- 或者添加
ifndef __文件名_H__
定义:
数据类型 *变量名
是一种数据类型,用来指向一个变量的地址
使用:
p = &变量名
- 接收该变量的地址*p = 数值
- 给这个指针赋值。- 取地址
p
取值*p
#include<stdio.h> int main() { int a = 10; int *p = &a; // 将a的地址复制给p *p = 110; // 给p赋值相等于给a赋值 return 0; }
指针在内存中的大小
通过
sizeof
对指针内存进行判断可知
在32位的系统中,所有的指针大小都是
4
个字节
64位系统,8
字节
#include<stdio.h> int main() { int a = 10; int *p = &a; // 将a的地址复制给p printf("%d\n", sizeof(p)); printf("%d\n", sizeof(char *)); return 0; }
野指针
指向一个
未知的内存空间
,可能在读写的时候出现错误。
系统规定:0-255之间的地址是系统保留的,阔以读,但不能写
。
空指针
指向内存
编号0
的空间, 操作该内存空间会报错,通常作为条件判断使用
万能指针
是
void
类型,如果赋值的话,需要强转类型
#include<stdio.h> int main() { int a = 10; void *p = &a; * (int *)p = 110; printf("%d", * (int *)p); return 0; }
const
修饰常量:可以使用指针去修改他的值。const int a = 100;
const
修饰 数据类型 *
不能改变指针变量指向的内存地址的值,可改变地址。const int *p;
const
修饰 指针变量
能改变值,不能改变地址。int * const p;
const
修饰 数据类型
和 指针变量
都不能改变const int * const p;
数组取值操作
当一个数组赋值给指针时指针有俩种取值方式:
- 使用下标进行取值例如:
p[1];
- 使用
*(p+i);
通过循环可对数组逐一取值。
地址移动
当对p进行操作如:
p++; p = p+1;
此时的p不在指向原来的地址,若原本赋值一个数组,则此时p指向的时首地址的下一个地址。
小案例:指针冒泡排序
#include<stdio.h> // 冒泡排序 void get(int *p, int len) { int i, j; for (i = 0; i<len -1; i++) { for (j=0; j<len-1-i; j++) { int temp; if (p[j] < p[j+1]) // 判断后一位是否大于前一位 { temp = p[j]; p[j] = p[j+1]; p[j+1] = temp; } } } } int main() { int arr[] = {7,0,9,2,3,1,5,6,4,8}; int len = sizeof(arr) / sizeof(arr[0]); // 获取数组长度 int *p = arr; get(p, len); // 调用冒泡排序函数 for (int i=0; i<10; i++) { printf("%d", arr[i]); // 打印结果 } return 0; }
相当于指针对数组进行操作
- 字符串常量是一个常量数组,可以读取,但不能修改。
#include<stdio.h> int main() { char *src = "hello"; // 位于常量区不能修改 char srcs[] = "hellow"; // 位于栈区可对字符串进行修改 }
以上是对函数以及指针的总结,后面对于字符串的内容相对较少,但与前面的内容没啥差别。就不过多的进行介绍。学完这部分内容,可以尝试着写出c语言的字符串的内置函数,例如
读取长度
、字符串对比
等,通过练习来提高对该内容的掌握。⛽