C 程序是有函数组成的, 代码都是由主函数 main() 开始执行的. 函数是 C 程序的基本模块, 适用于完成特定任务的程序代码单元.
从函数定义的角度看, 函数可分为系统函数和用户定义函数两种
函数定义的一般形式:
返回类型 函数名(形式参数列表)
{
数据定义部分;
执行语句部分;
}
int max(int a, int b)
{
int c = 0;
if(a > b){
c = a;
}else{
c = b;
}
return c;
}
/*
函数名: 用户定义的标识符,类似于给一个变量起名字
返回类型: 如有返回值, 函数体里通过return返回, return后面的变量尽量和返回类型匹配,如代码中的 int
形参列表: 可以是给类型的变量, 各参数之间用逗号间隔
函数体:大括号{}内的内容即为函数体内容,一般有数据定义,语句执行,return返回值
*/
函数名
理论上是可以随意起名字,最好起的名字见名知意,应该让用户看到这个函数名就知道这个函数的功能. 注意: 函数名后面有个圆括号(), 代表这个味函数, 不是普通的变量名.
形参列表
在定义函数时指定的形参, 在未调用函数时,它们并不占内存中的存储单元, 因此称他们是形式参数或虚拟参数, 简称形参, 表示并不时实际存在的数据, 所以形参里变量不能赋值
在定义函数时指定的形参,必须是 类型+变量 的形式:
//1: right, 类型+变量
void max(int a, int b)
{
}
//2: error, 只有类型,没有变量
void max(int, int)
{
}
//3: error, 只有变量,没有类型
int a, int b;
void max(a, b)
{
}
在定义函数时指定的形参和返回值根据函数的需要来设计,如果没有形参,圆括号内容为空,或写一个 void 关键字
花括号{ } 里的内容即为函数体的内容, 这里为函数功能实现的过程,这和之前的代码没太大区别, 之前我们把代码写在 main ()函数里,现在只是把这些写到别的函数里.
返回值
函数的返回值是通过函数中的 return 语句获得的, return 后面的值也可以是一个表达式
尽量保证 return 语句中表达式的值和函数返回类型是同一类型.
int max() // 函数的返回值为int类型
{
int a = 10;
return a;// 返回值a为int类型,函数返回类型也是int,匹配
}
如果函数返回的类型和 return 语句中表达式的值不一样,则以函数返回类型为准, 即**函数返回类型决定返回值的类型.**返回值类型可以自动进行类型转换.
double max() // 函数的返回值为double类型
{
int a = 10;
return a;// 返回值a为int类型,它会转为double类型再返回
}
注意: 如果函数返回的类型和 return 语句中表达式的值不一致,而他又无法自动进行类型转换, 程序则会报错.
return 语句的另一个作用为中断 return 所在的执行函数, 类似于 break 终端循环和 switch 语句一样.
int max()
{
return 1;// 执行到,函数已经被中断,所以下面的return 2无法被执行到
return 2;// 没有执行
}
如果函数带返回值, return 后面必须跟着一个值, 如果函数没有返回值, 函数名字的前面必须写一个 void 关键字, 这时候, 我们写代码时也可以通过 return 中断函数, 只是这时, return 后面不带内容(分号除外)
函数后, 我们需要调用此函数才能执行到这个函数里的代码. 这和 main函数不一样, main () 为编译器设定好自动调用的主函数, 无需人为调用, 我们都是在 main() 函数里面调用别的函数, **一个 C 程序里有且只有一个 main() 函数.
#include
void print_test()
{
printf("this is for test\n");
}
int main()
{
print_test(); // print_test函数的调用
return 0:
}
如果是调用无参函数, 则不能加上 “实参” , 但括号不能省略
// 函数的定义
void test()
{
}
int main()
{
// 函数的调用
test(); // right, 圆括号()不能省略
test(250); // error, 函数定义时没有参数
return 0;
}
如果是参列表包含多个实参, 则各参数间用逗号隔开.
// 函数的定义
void test(int a, int b)
{
}
int main()
{
int p = 10, q = 20;
test(p, q); // 函数的调用
return 0;
}
实参与形参的个数应相等, 类型应匹配. 实参与形参按顺序对应, 一对一地传递数据.
实参可以是常量,变量,或表达式, **无论是餐是何种类型的量, 在进行函数调用时, 他们都必须具有确定的值, 以便把这些值传送给形参. **所以, 这里的变脸是在圆括号 ( ) 外面定义好, 赋好值的变量.
// 函数的定义
void test(int a, int b)
{
}
int main()
{
// 函数的调用
int p = 10, q = 20;
test(p, q); // right
test(11, 30 - 10); // right
test(int a, int b); // error, 不应该在圆括号里定义变量
return 0;
}
如果函数定义没有返回值, 函数调用时不能写 void 关键字, 调用函数时也不能接收函数的返回值.
// 函数的定义
void test()
{
}
int main()
{
// 函数的调用
test(); // right
void test(); // error, void关键字只能出现在定义,不可能出现在调用的地方
int a = test(); // error, 函数定义根本就没有返回值
return 0;
}
如果函数定义有返回值,这个返回值我们根据用户需要可用可不用, 假如需要使用函数返回值, 我们需要定义一个匹配类型的变量来接受
// 函数的定义, 返回值为int类型
int test()
{
}
int main()
{
// 函数的调用
int a = test(); // right, a为int类型
int b;
b = test(); // right, 和上面等级
char *p = test(); // 虽然调用成功没有意义, p为char *, 函数返回值为int, 类型不匹配
// error, 必须定义一个匹配类型的变量来接收返回值
// int只是类型,没有定义变量
int = test();
// error, 必须定义一个匹配类型的变量来接收返回值
// int只是类型,没有定义变量
int test();
return 0;
}
如果使用用户自己定义的函数, 而该函数与调用他的函数(主调函数) 不在同一文件中, 或者**函数定义的位置在主调函数之后, 则必须在调用此函数之前对被调的函数作声明.
所谓函数声明, 就是在函数尚未定义的情况下, 实现将该函数的有关信息功过编译系统, 相当于告知编译器, 函数在后面定义, 以便使编译能正常进行.
**注意:**一个函数只能被定义一次, 但可以声明多次.
#include
int max(int x, int y); // 函数的声明,分号不能省略
// int max(int, int); // 另一种方式
int main()
{
int a = 10, b = 25, num_max = 0;
num_max = max(a, b); // 函数的调用
printf("num_max = %d\n", num_max);
return 0;
}
// 函数的定义
int max(int x, int y)
{
return x > y ? x : y;
}
在 main 函数中调用 exit 和 return 结果是一样的, 但在职函数中调用 return 只是代表子函数终止了, 在子函数中调用 exit ,那么程序终止.
#include
#include
void fun()
{
printf("fun\n");
//return;
exit(0);
}
int main()
{
fun();
while (1);
return 0;
}
main.c
#include
#include
int main(void)
{
int a = 10, b = 20, max_num, min_num;
max_num=max(a,b);
min_num=min(a,b);
printf(.......)
}
fun.c
int max(int x, int y)
{
return x>y?x:y;
}
int min(int x, int y)
{
return x<y?x:y;
}
func.h
extern int max(int x, int y);
extern int max(int x, int y);
但一个项目比较大时, 往往都是分文件, 这时候又可能不小心把同一个头文件 include 多次, 或者头文件嵌套包含.为了避免头一个文件被 include 多次, C/C++中有两种方式,一种是 #ifdef 方式, 一种是 #pragma once 方式.
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
// 声明语句
#endif
#pragma once
// 声明语句
内存含义:
内存是沟通 CPU 与硬盘的桥梁:
有关内存的两个概念: 物理存储器和存储地址空间.
物理存储器:实际存在的具体存储器芯片.
储存地址空间: 对储存器编码的范围. 我们在软件上常说的内存是指这一层含义.
内存中的每一个数据都会分配相应的地址:
#include
int main()
{
int a = 0;
char b = 100;
printf("%p, %p\n", &a, &b); //打印a, b的地址
//int *代表是一种数据类型,int*指针类型,p才是变量名
//定义了一个指针类型的变量,可以指向一个int类型变量的地址
int *p;
p = &a;//将a的地址赋值给变量p,p也是一个变量,值是一个内存地址编号
printf("%d\n", *p);//p指向了a的地址,*p就是a的值
char *p1 = &b;
printf("%c\n", *p1);//*p1指向了b的地址,*p1就是b的值
return 0;
}
注意 :& 可以去的一个变量在内存中的地址. 但是, 不能去寄存器变量, 因为寄存器变量不在内存里, 而在 CPU 里面, 所以是没有地址的.
int *p1;
int **p2;
char *p3;
char **p4;
printf("sizeof(p1) = %d\n", sizeof(p1));
printf("sizeof(p2) = %d\n", sizeof(p2));
printf("sizeof(p3) = %d\n", sizeof(p3));
printf("sizeof(p4) = %d\n", sizeof(p4));
printf("sizeof(double *) = %d\n", sizeof(double *));
注意: 定义指针的类型与所指向地址存储的数据类型相一致, 此数据类型告诉指针所指向空间的大小.
指针变量也是变量, 是变量就可以任意赋值, 不要越界即可(32 位为 4 字节, 64 位为 8 字节), 但是, 任意数值赋值给指针变量没有意义, 因为这样的指针就成了野指针, 此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域). 所以, 野指针不会直接引发错误, 操作也指针指向的内存区域才会出问题
int a = 100;
int *p;
p = a; //把a的值赋值给指针变量p,p为野指针, ok,不会有问题,但没有意义
p = 0x12345678; //给指针变量p赋值,p为野指针, ok,不会有问题,但没有意义
*p = 1000; //操作野指针指向未知区域,内存出问题,err
但是, 野指针和有效指针变量保存的都是数值, 为了标志此指针变量没有指向任何变量 (空闲可以用) , C 语言中, 可以吧 NULL 赋值给此指针, 这样就标志此指针为空指针, 没有任何指向. (NULL 是一个值为 0 的宏常量)
void * 指针可以指向任意变量的内存空间:
void *p = NULL;
int a = 10;
p = (void *)&a; //指向变量时,最好转换为void *
//使用指针变量指向的内存时,转换为int *
*( (int *)p ) = 11;
printf("a = %d\n", a);
int a = 100;
int b = 200;
//指向常量的指针
//修饰*,指针指向内存区域不能修改,指针指向可以变
const int *p1 = &a; //等价于int const *p1 = &a;
//*p1 = 111; //err
p1 = &b; //ok
//指针常量
//修饰p1,指针指向不能变,指针指向的内存可以修改
int * const p2 = &a;
//p2 = &b; //err
*p2 = 222; //ok
数组名字是数组的首元素地址, 但是他是一个常量:
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
printf("a = %p\n", a);
printf("&a[0] = %p\n", &a[0]);
//a = 10; //err, 数组名只是常量,不能修改
#include
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int i = 0;
int n = sizeof(a) / sizeof(a[0]);
for (i = 0; i < n; i++)
{
//printf("%d, ", a[i]);
printf("%d, ", *(a+i));
}
printf("\n");
int *p = a; //定义一个指针变量保存a的地址
for (i = 0; i < n; i++)
{
p[i] = 2 * i;
}
for (i = 0; i < n; i++)
{
printf("%d, ", *(p + i));
}
printf("\n");
return 0;
}
#include
int main()
{
int a;
int *p = &a;
printf("%d\n", p);
p += 2;//移动了2个int
printf("%d\n", p);
char b = 0;
char *p1 = &b;
printf("%d\n", p1);
p1 += 2;//移动了2个char
printf("%d\n", p1);
return 0;
}
通过改变指针指向操作数组元素:
#include
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int i = 0;
int n = sizeof(a) / sizeof(a[0]);
int *p = a;
for (i = 0; i < n; i++)
{
printf("%d, ", *p);
p++;
}
printf("\n");
return 0;
}
减法运算
示例1:
#include
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int i = 0;
int n = sizeof(a) / sizeof(a[0]);
int *p = a+n-1;
for (i = 0; i < n; i++)
{
printf("%d, ", *p);
p--;
}
printf("\n");
return 0;
}
示例2:
#include
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int *p2 = &a[2]; //第2个元素地址
int *p1 = &a[1]; //第1个元素地址
printf("p1 = %p, p2 = %p\n", p1, p2);
int n1 = p2 - p1; //n1 = 1
int n2 = (int)p2 - (int)p1; //n2 = 4
printf("n1 = %d, n2 = %d\n", n1, n2);
return 0;
}
指针数组,他是数组,数组的每个元素都是指针类型
#include
int main()
{
//指针数组
int *p[3];
int a = 1;
int b = 2;
int c = 3;
int i = 0;
p[0] = &a;
p[1] = &b;
p[2] = &c;
for (i = 0; i < sizeof(p) / sizeof(p[0]); i++ )
{
printf("%d, ", *(p[i]));
}
printf("\n");
return 0;
}
int a = 10;
int *p = &a; //一级指针
*p = 100; //*p就是a
int **q = &p;
//*q就是p
//**q就是a
int ***t = &q;
//*t就是q
//**t就是p
//***t就是a
#include
void swap1(int x, int y)
{
int tmp;
tmp = x;
x = y;
y = tmp;
printf("x = %d, y = %d\n", x, y);
}
void swap2(int *x, int *y)
{
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int a = 3;
int b = 5;
swap1(a, b); //值传递
printf("a = %d, b = %d\n", a, b);
a = 3;
b = 5;
swap2(&a, &b); //地址传递
printf("a2 = %d, b2 = %d\n", a, b);
return 0;
}
数组名做函数参数, 函数的形参会退化为指针:
#include
//void printArrary(int a[10], int n)
//void printArrary(int a[], int n)
void printArrary(int *a, int n)
{
int i = 0;
for (i = 0; i < n; i++)
{
printf("%d, ", a[i]);
}
printf("\n");
}
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int n = sizeof(a) / sizeof(a[0]);
//数组名做函数参数
printArrary(a, n);
return 0;
}
#include
int a = 10;
int *getA()
{
return &a;
}
int main()
{
*( getA() ) = 111;
printf("a = %d\n", a);
return 0;
}
#include
int main()
{
char str[] = "hello world";
char *p = str;
*p = 'm';
p++;
*p = 'i';
printf("%s\n", str);
p = "mike jiang";
printf("%s\n", p);
char *q = "test";
printf("%s\n", q);
return 0;
}
#include
void mystrcat(char *dest, const char *src)
{
int len1 = 0;
int len2 = 0;
while (dest[len1])
{
len1++;
}
while (src[len2])
{
len2++;
}
int i;
for (i = 0; i < len2; i++)
{
dest[len1 + i] = src[i];
}
}
int main()
{
char dst[100] = "hello mike";
char src[] = "123456";
mystrcat(dst, src);
printf("dst = %s\n", dst);
return 0;
}
#include
#include
#include
int main(void)
{
//const修饰一个变量为只读
const int a = 10;
//a = 100; //err
//指针变量, 指针指向的内存, 2个不同概念
char buf[] = "aklgjdlsgjlkds";
//从左往右看,跳过类型,看修饰哪个字符
//如果是*, 说明指针指向的内存不能改变
//如果是指针变量,说明指针的指向不能改变,指针的值不能修改
const char *p = buf;
// 等价于上面 char const *p1 = buf;
//p[1] = '2'; //err
p = "agdlsjaglkdsajgl"; //ok
char * const p2 = buf;
p2[1] = '3';
//p2 = "salkjgldsjaglk"; //err
//p3为只读,指向不能变,指向的内存也不能变
const char * const p3 = buf;
return 0;
}
int main(int argc, char *argv[]);
#include
//argc: 传参数的个数(包含可执行程序)
//argv:指针数组,指向输入的参数
int main(int argc, char *argv[])
{
//指针数组,它是数组,每个元素都是指针
char *a[] = { "aaaaaaa", "bbbbbbbbbb", "ccccccc" };
int i = 0;
printf("argc = %d\n", argc);
for (i = 0; i < argc; i++)
{
printf("%s\n", argv[i]);
}
return 0;
}
定义 | 说明 |
---|---|
int i | 定义整形变量 |
int *p | 定义一个指向int的指针变量 |
int a[10] | 定义一个有10个元素的数组,每个元素类型为int |
int *p[10] | 定义一个有10个元素的数组,每个元素类型为int* |
int func() | 定义一个函数,返回值为int型 |
int *func() | 定义一个函数,返回值为int *型 |
int **p | 定义一个指向int的指针的指针,二级指针 |