一.函数:子程序
C中函数分类:库函数、自定义函数
库包括:IO函数、字符串操作、字符、内存、时间/日期、数学、其他
size_t无符号整形
为什么a、 b没有交换值?
#include <stdio.h>
int main()
void swap(int a, int b)
{
int z = 0;
z = x;
x = y;
y = z;
}
{
int a = 0;
int b = 0;
scanf("%d%d", &a, &b);
swap(a,b);
printf("a=%d,b=%d", a, b);
return 0;
}
因为形参是实参的一份临时拷贝,但形参有自己独立空间,因为有独立空间所以改写形参不会影响实参。
函数调用分为传值调用(ADD)和传址调用(swap)
传值函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
传址:把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
此方式可让函数与其他外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部变量 。
数组传参实质上传递的是数组首元素的地址而不是整个数组。
所以在函数内部计算一个函数参数部分的数组的元素个数是不可靠的。
形参arr看上去是数组,本质上是指针变量。
嵌套调用与链式访问
函数与函数之间地位相等,所以不可以相互嵌套。
链式访问:将一个函数的返回值作为其他函数的参数
经典程序:
#include <stdio.h>
int main()
{
printf("%d",printf("%d",printf("%d",43)));
return 0;
}
打印结果:4321
原因:printf返回值是打印字符个数。
函数不写返回值时,默认返回类型是int
main()函数有三个参数:int argc, char* argv[ ], char *envp[ ].
函数声明和定义:
函数声明:
1.告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但具体是不是存在,函数声明决定不了。
一般出现在函数使用之前,要满足先声明后使用。
一般要放在头文件中。
函数定义L:交代函数具体实现
声明定义分开好处:
(1)模块化,方便共同合作
(2)设置成静态库保护产权。
递归:函数/一个过程在其定义或说明中有直接或间接调用自身的一种方法。
通常将一个大型复杂问题层层转化为一个与原问题相似的规模较小的问题来求解。
目的:只需要少量程序可描述出解题过程所需要的多次重复计算,大大减少代码量。
大事化小。核心思想是拆分。
递归需要有限定条件,否则会陷入死循环。
每次需要越来越接近递归条件。
斐波那契公式:Fib(n) = 1, n <=1;Fib(n-1) +Fib(n-2), n>2
switch语句中default可以随意放(在正常语法下)
二、数组:是一组相同类型元素的集合。
c99之前,数组大小必须是常量/常量表达式
C99之后,可以是变量,为了支持变长数组(通过变量来指定)。
不完全初始化,剩余的元素默认初始化为0
char ch1[10] = {'a', 'b', 'c'};
// a b c 0 0 0 0 0 0 0(7个0)
char ch2[10] = "abc";
//a b c \0 0 0 0 0 0 0(6个0, \0占一个内存单元)
char ch1[] = {'a', 'b', 'c'};
ch1放进去3个元素
char ch2[] = "abc";
ch2放进去四个元素
数组元素在连续的空间上存放
二维数组
char arr[] []
行可以省略,列不可以省略
为什么二维数组不能直接使用scanf函数,而是在for循环里使用?
因为要定位到每个元素,保证每一个值都能被锁定到,如果直接使用scanf要写个语句。
可以将二维数组理解为:一维数组的数组。
数组越界编译器不一定会报错。
冒泡排序算法:
核心思想:两个相邻元素进行比较一趟冒泡排序让一个数据来到它最终应该出现的位置上。
数组传参,形参有两种写法:
(1)数组; (2)指针
形参所在函数不可求元素个数,因为函数穿的是地址。
数组名能表示首个元素地址但又两个例外:
(1)sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
(2)&数组名,这里的数组名表示整个数组,去除的是整个数组的地址,二维数组的数组名也表示数组首元素地址(一行的地址,第一行)。
数组有类型,去掉名字剩下就是类型,如int arr[2] 中,arr 类型是int [2].
sizeof会将字符串中\0计算在内,strlen不计算\0.
sizeof是一个操作符,用来计算变量(类型)所占内存空间大小,不关注具体内容
strlen是一个库函数,是专门求字符串长度的,只能针对字符串
操作符
&、|、^按二进制计算。
|(或):有1得1,同时为0才为0.
&与:有0得0
^异或:相同为0,不同为1
将2进制中某一位改成1用移位操作符
如:a = 13;
a |= (1<<1);(表示把1左移1位再与a或);
#include <stdio.h>
int main()
{
int a = 13;
a |= (1 << 4);
printf("%d", a);//结果29
a &=(~(1 << 4));
printf("%d", a); // 13
return 0;
}
无论什么类型的指针大小都是4/8个字节,比较两个字符串是否相等应该用strcmp.不能用==。
"abc" == "abcdef"是在比较2个字符串得首字符的地址。
[ ]是取元素下角标操作符
数组和指针的等价表示方法:
arr[7] <==>*(arr+7)
arr是数组首元素的地址。
arr+7就是跳过七个元素,只想第8个元素
*(arr+7)就是第8个元素
arr[7] <==> 7[arr]
struct结构体中图给变量赋名:
strcpy(ss.name, "zhangsan");(此赋名方法正确)
ss.name = "zhangsan" (错误)
定义结构体时已经给成员变量赋值,所以不能用=,得用strcpy
隐式类型转换:
整形提升:C得整形算术运算总是至少以缺省整型类型的精度来进行的。为了获得此精度,表达式中字符与短整型操作数在使用之前被转换位普通整形,这种转换被称为整型提升。
CPU整形运算器(ALU)操作数字节长度一般就是int的字节长度,同时也是CPU通用寄存器长度一般就是int的字节长度,同时也是CPU通用寄存器长度。
算数转换时向大的类型转换
eax ebx ecx edx是寄存器
scanf(" %c", &ch);空格的作用:跳过字符之前所有空白字符
指针式内存中最小单元的编号,我们把内存单元的编号称为地址,地址就是指针,指针通常指的是指针变量,用来存放内存地址的变量
内存单元地址不需要存起来,硬件直接生成
指针变量,里面存放的是地址,二通过这个地址,就可以找到一个内存单元
结论1:指针类型界定了指针在被解引用的时候访问几个字节
int* 解引用访问4个字节
结论2:指针类型决定了指针±1操作的时候,跳过几个字节,决定了指针的步长。
总结:指针有变量类型方便访问。
野指针:之指针指向的位置是不可知的。
int* p;p未初始化,就意味着没有明确指向
一个局部变量不初始化的话,放的是随机值:0xccccc
*p=10;//非法访问内存,这里的p就是野指针
NULL空指针不可以被访问
正确使用方法:
int* p3 = NULL;
if(p3 != NULL)
{
*p3 = 100;
}
防止越界访问
避免野指针方法:
(1)指针初始化
(2)小心指针越界
(3)指针指向空间释放即使置NULL
(4)避免返回局部变量的地址
(5)指针使用之前检查有效性
|指针-指针| = 指针与指针之间的元素个数,不是所有指针都能相减
指向同一块空间的两个指针可以相减
标准规定:允许指针前边元素与后边比较,但不允许后边与指向第一个元素之前的内存位置的指针比较
简单来说可以向后越界比较,后边不可以与前边越界比较
可以通过指针来访问数组名
printf arr[i] <==> printf *(p+i) <==> printf(arr+i)
二级指针就是用于存放指针变量的地址
int a = 10;
int* pa = &a;//pa为一级指针
int** ppa = &pa;//ppa为二级指针
第二个*说明ppa是指针
int*说明ppa指向的对象是int*类型
指针数组:存放指针的数组
int* parr[10] = {&a, &b, &c};
结构体:
结构是一些值得集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
一个汉字占两个字符
#include <stdio.h>
int main()
{
struct tag//标签
{
member-list;//成员变量
} variable-list;
return 0;
}
//如:
struct peo
{
char name[20];
char tele[12];
char sex[5];
int high;
}p1, p2;