1.函数
1.函数的定义
函数名是该函数的地址,所以在取址时可以不写&,直接写函数名,但是为了区分,最后写上
void print_c() ; // 声明函数
int main()
{
print_c() ;
}
void print_c()
{
printf(" ###### \n") ;
printf("## ##\n") ;
printf("## \n") ;
printf("## \n") ;
printf("## ##\n") ;
printf(" ####### \n") ;
}
1.2函数的声明
1.3函数的参数和返回值
案例,求累加和
int sum(int n) ;
int main()
{
int n ;
scanf("%d",&n) ;
printf("%d",sum(n)) ;
}
int sum(int n)
{
int res ;
for(int i = 1; i <= n ; i++)
{
res += i ;
}
return res ;
}
2.参数和返回值
2.1形参和实参
形式参数和实际参数
形参:只有在函数调用的内部有效,是在定义函数时写的参数
实参:是真正调用时,传递的参数为实参
2.2传值和传址
2.2.1 传递的变量为值
void swap(int x , int y) ;
void swap(int x, int y)
{
printf("in swap 互换前: x = %d, y = %d\n",x,y) ;
int temp = x ;
x = y ;
y = temp ;
printf("in swap 互换后:x = %d, y = %d\n", x,y) ;
}
int main()
{
int x = 3 , y = 5;
printf("in main 互换前: x = %d, y = %d\n",x,y) ;
swap(x,y) ;
printf("in main 互换后: x = %d, y = %d\n",x,y) ;
}
2.2.2 传递的变量为指针
void swap(int *x , int *y) ;
void swap(int *x, int *y)
{
printf("in swap 互换前: x = %d, y = %d\n",*x,*y) ;
int temp = *x ;
*x = *y ;
*y = temp ;
printf("in swap 互换后:x = %d, y = %d\n", *x,*y) ;
}
int main()
{
int x = 3 , y = 5;
printf("in main 互换前: x = %d, y = %d\n",x,y) ;
swap(&x,&y) ;
printf("in main 互换后: x = %d, y = %d\n",x,y) ;
}
==注意==
可以看出以上的两个结果,是不一样的
- 在传递的变量是值的时候,swap函数之后,发现在main函数中,x,y的值并没有改变。
- 这是因为每个函数都有独立的作用域,简单的理解就是,每个函数内部都是互相独立的。他们的变量只在函数内部生效,不同的函数,不在同一个作用域里面,是不同的两组变量
- 但是,址传递的时候,main函数中x,y的值也会改变,址传递,是将这两个数的地址直接互换的,像任意门一样,给拉过来了,不考虑作用域。
2.3传数组
注意
:传数组,并不是将整个数组传递过去的,通过下面的案例可以发现。在get_array函数里改变数组的值,在main函数中有发生了改变。说明并不存在将整个数组作为参数传递的这么一个方式。传递的是数组的第一个元素的地址
void get_array(int a[10]) ;
void get_array(int a[10]) // 这里接受的只是数组的第一个元素的地址。
{
a[5] = 520 ;
for(int i = 0; i < 10 ; i ++)
{
printf("a[%d] = %d\n",i,a[i]);
}
}
int main()
{
int a[10] = {1,2,3,4,5,6,7,8,9,0} ;
get_array(a) ;
printf("在main函数里再打印此意 \n") ;
for(int i = 0 ; i < 10 ; i++)
{
printf("a[%d] = %d\n",i,a[i]); // 发现再main函数里,a[5]的值也发生了改变
//说明并不存在将整个数组作为参数传递的这么一个方式。
}
}
2.3.1传数组传递的是第一个元素的地址
void get_array(int b[10]) ;
void get_array(int b[10])
{
printf("size of b = %d\n",sizeof(b)) ;
}
int main()
{
int a[10] = {1,2,3,4,5,6,7,8,9,0} ;
printf("size of a = %d\n",sizeof(a)) ;
get_array(a) ;
}
通过值结果可以证明,传递的并不是整个数组而是数组的第一个元素的地址。
2.4可变参数
如果要有可变参数,需要有一个头文件。
int printf(const char format,…)
int scanf(const char format,…)就拿 printf 来说吧,它除了有一个参数 format 固定以外,后面的参数其个数和类型都是可变的,用三个点“…”作为参数占位符
三个…代表的意思是作为参数的占位符
#include<stdio.h>
#include<stdarg.h>
int sum(int n ,...) ;//第一个参数是指定后面有多少个参数
int sum(int n,...)
{
int i , sum = 0 ;
va_list vap ; // 可变的列表,定义为vap
va_start(vap,n) ;// 初始化这个参数列表 ,需要两个参数,一个是valist,一个是n
for(i = 0 ; i < n ; i++)
{
sum += va_arg(vap,int) ; //获取list参数里的,每个的值,要写清楚类型
}
va_end(vap) ; // 收尾工作,关闭参数列表
return sum ;
}
int main()
{
int res ;
res = sum(3,1,2,3) ;
printf("res = %d\n",res) ;
}
3.指针函数和函数指针
3.1指针函数
案例
char *getWord(char c) ;
char *getWord(char c) //返回的值是字符串,通常用char类型的指针来定义字符串
{
switch(c)
{
case 'a' : return "apple" ; // 其实返回的是字符串首字母的地址
case 'b' : return "banana" ;
case 'c' : return "cat" ;
case 'd' : return "dog" ;
default: return "none" ;
}
}
int main()
{
char input ;
printf("请输入一个字母\n") ;
scanf("%c",&input) ;
printf("%s\n",getWord(input)) ;
}
不要返回局部变量的地址
3.2函数指针
函数指针 返回值类型 (*p)(传递的参数的类型)
int square(int num) ;
int square(int num)
{
return num*num ;
}
int main()
{
int num ;
int (*fp)(int) ; // 这里是等价于int square的,所以下面可以相等。 定义的是函数指针
//整形的返回值, 整形的参数,函数指针要与函数的各部分相对应。
scanf("%d",&num) ;
fp = square ;
printf("%d * %d = %d\n", num,num,(*fp)(num)) ;
}
3.3函数指针作为参数
可以这样理解,理解为函数指针作为参数就是,该指针指向了某个函数,例如下面的,指向了add 和 sub
int add(int , int) ;
int sub(int , int) ;
int calc(int (*fp)(int , int),int,int); // 函数指针
int add(int num1 , int num2 )
{
return num1+num2 ;
}
int sub(int num1,int num2)
{
return num1-num2 ;
}
int calc(int (*fp)(int ,int),int num1, int num2) //函数指针作为参数 ,关键的一步。有点类似于java中的各个方法调用的意思。hhh
{
return (*fp)(num1,num2) ;
}
int main()
{
printf("3 + 5 = %d\n",calc(add,3,5)) ;
printf("3 - 5 = %d\n",calc(sub,3,5)) ;
}
3.4函数指针作为返回值
==注意== 函数指针作为返回值的时候,需要定义一个函数指针来接受该返回值
函数指针作为返回值,这个返回值又会指向其他的函数,比如下面的例子,指向了add和sub
int add(int num1 , int num2 )
{
return num1+num2 ;
}
int sub(int num1,int num2)
{
return num1-num2 ;
}
int calc(int (*fp)(int , int), int num1 , int num2) // 函数指针作为参数
{
return (*fp)(num1,num2) ;
}
int (*select(char op))(int ,int) // 函数指针作为返回值,指向的函数的参数个数类型要保持一致。
{
switch(op)
{
case '+' : return add;
case '-' : return sub ;
}
}
int main()
{
int num1, num2 ;
char op ;
int (*fp)(int ,int) ; // select是函数指针,这里定义一个函数指针来接受
printf("请输入一个式子(如1+3):") ;
scanf("%d%c%d",&num1,&op,&num2) ;
fp = select(op) ;
printf("%d %c %d = %d \n",num1,op,num2,calc(fp,num1,num2)) ;
}
3.5函数指针作为参数和作为返回值之间的差别
定义的形式上
- 作为参数:
int calc(int (*fp)(int ,int),int num1, int num2)
- 作为返回值:
int (*select(char op))(int ,int)
- 作为参数:
意义:
作为参数是:调用了某个其他的函数,参数的类型是要保持一致的
作为返回值:是指向了某个其他的函数,在main函数中需要有函数指针接受这个返回值,参数类型也要跟指向的其他函数保持一致
4.局部变量和全局变量
4.1全局变量
在主函数外定义的变量,就是全局变量了。
如果不对全局变量初始化,,那么它会自动初始化为0
!! 不要大量的使用全局变量
4.2 extern关键字
void func()
{
extern cnt ;
cnt++ ;
}
int cnt = 0 ;
int main()
{
func() ;
printf("%d",cnt) ;
}
5.作用域和链接属性
5.1作用域
5.1.1代码块作用域
很容易理解,这里就不举例子了。
5.1.2文件作用域
省略
5.1.3原型作用域
声明函数的时候可以不用和定义的函数的参数名一样,只写参数的类型就好了。
5.2链接属性
不同函数定义在不同的文件中,但是并不影响main函数的调用声明的时候 extern int example
写在文件头就可以了。就可以用example这个函数。
就是可以把函数放在另一个文件,需要的时候就可以调用,这样可以使得主函数main不那么杂乱
只有具备文件作用域的标识符才能拥有external或internal的链接属性其他作用域的标识符都是none属性
如果函数前面加上static,该函数只能在本文件中调用,其他文件不能调用,因为此时它变为了internal属性。
6.定义和声明
7.变量的生存期和存储类型
7.1生存期
7.2存储类型
作用域和生存期其实是由存储类型决定的。
c语言中提供了5种不同的存储类型
- auto
- register
- static
- extern
- typedef
7.2.1 auto
自动变量拥有代码块作用域,空链接属性,自动存储期
7.2.2 register
寄存器变量
7.2.3 static静态局部变量
static声明变量,可以将exernal的变量变为internal的变量,多文件的作用域变为单文件的作用域
static声明局部变量,可以将局部变量指定为静态局部变量。
static使得局部变量具有静态存储期,所以他的生存期与全局变量一样,直到程序结束才释放。
案例
void func()
{
int cnt = 0 ;
printf("%d\n",cnt) ;
cnt++ ;
}
int main()
{
for(int i = 0 ; i<10 ; i++)
{
func();
}
}
上面的cnt没有加static ,生存期为自动存储期,通过调用函数可以发现,输出的值全为0,因为每次调用结束就释放cnt,并不会进行累加
void func()
{
static int cnt = 0 ;
printf("%d\n",cnt) ;
cnt++ ;
}
int main()
{
for(int i = 0 ; i<10 ; i++)
{
func();
}
}
通过结果对比可以发现,static使得局部变量有了静态存储期,跟全局变量一样,知道最终程序结束才释放。
7.2.4 static 和 extern
8.递归
属于算法的范畴,详细笔记参考算法对应的章节。
8.1实现汉诺塔
//递归的方式实现汉诺塔
void hanoi(int n, char x ,char y , char z)
{
if(n == 0) return ;
hanoi(n-1,x,z,y); // 最后将第二根柱子的所有盘移动到第三根柱子
printf("%c --> %c\n",x,z) ; // 第一根柱子移动到第三根柱子
hanoi(n-1,y,x,z) ; //第一根的所有柱子移动到第二根柱子,然后将最大的盘移动到第三根柱子。
}
int main()
{
int n ;
printf("请输入汉诺塔的层数") ;
scanf("%d",&n) ;
hanoi(n,'x','y','z') ;
}
9.快速排序
参考算法笔记,分治那一章