C语言学习笔记
一、C语言
- 算法的特性
有穷性:有限的操作步骤
确定性:每一个步骤是确定的
有输入:零个或多个
有输出:一个或多个
有效性:每个步骤都能有效执行 - 结构化程序设计
结构化程序:高级语言表示的结构化算法
结构化程序设计方法的思路:把一个复杂问题的求解过程,分段进行,每个阶段的处理控制在容易理解和处理的范围内
方法:自顶向下,模块化设计(自顶向下,逐步细化;自下而上,逐步积累)
二、数据类型
- 常量
程序执行过程中,值不能被改变.
符号常量:#define PRICE 30 (此后出现PRICE都表示30),在其作用域不能改变,不能被赋值 - 变量
变量表示内存中具有特定属性的一个存储单元,用来存放数据。
变量名是以一个名字代表一个地址,编译系统给每一个变量名分配对于的内存地址。
变量名只能字母、数字、下划线,第一个必须是字母或下划线
区分大小写字母,长度最后不超过8个字符,先定义后使用 - 整型常量
八进制:以0开头,0123、-012
十六进制:以0x开头,0x123、-0x123 - 浮点型常量
表示形式:小数、指数
指数形式:(数字.)数字E/e+/-数字 - 字符常量
单引号包含一个字符
以\开头的特殊字符成转义字符
字符常量的存储,不是将字符本身存放,而是将字符对于ASCII代码放到存储单元中,这样字符型和整型可以通用,一个字符数据既可以以字符输出,也可以整数形式输出 - 强制类型转换
(类型名)(表达式):int(a%5)、(double)a - 自增自减
i++:先使用i的值,在i=i+1
++i:先i=i+1,再使用i的值 - 复合运算符
a+=3 等价于 a=a+3
x*=y+8 等价于 x=x*(y+8) - 逗号表达式
表达式1,表达式2:先求解1,再求解2
a=35,a4 先a=35=15,再a4,结果为60
三、C程序设计
- 输入输出
字符输出:putchar©; //c为字符或整型变量
字符输入:getchar©;
字符串输出:puts(str);
字符串输入:gets(str);
输出:printf(); //%md指定宽度 %m.nf 输出数据占m列,其中n位小数,如长度小于m,左端补空格
输入:scanf("%d",&a); //a是内存中的地址,&是地址运算符 - 选择结构
&&与:a&&b&&c 只要a为假就不判断b和c了
||或 :a||b||c 只要a为真就不判断b和c了
!非
条件运算符:(a>b)?a:b:如果为真,返回a,否则返回b
switch(表达式)
{ case 常量:语句1; //case ‘A’:printf(“85~100”);
default:语句;
} - 循环结构
while(表达式)语句:先判断表达式,后执行语句
do 语句 while(表达式); :先执行一次语句,后判断
break:跳出循环体,结束循环
continue:结束本次循环(即不执行本次循环剩下语句,开始下一次循环)
四、数组
1. 一维数组
数组名和变量名相同
举例:82页 Fibonacci数列、冒泡法排序
2. 二维数组
初始化
int a[2][3]={{1,2,3},{4,5,6}}; // 也可不加里面的花括号
int a[2][3]={{1},{2}};
int a[][3]={1,2,3,4,5,6}; //前面可省
举例:87页 行列互换、找最值
3. 字符数组
char c[5];
char c[4][5];
字符串结束标志:\0 字符串常量会自动加一个\0作为结束符,10个字符的有效字符为9个
初始化:char c[]=“I am a boy”;
char c[]={‘I’,’ ‘,‘a’,‘m’,’ ‘,‘a’,’ ‘,‘b’,‘o’,‘y’,’\0’};
scanf("%s",str); //不能获取带空格的字符串
gets(str); //可以获取带空格的字符串,缺点不会管str大小,容易污染内存
char *fgets(char *s, int size, FILE *stream) //可以获取带空格的,也可保证内存不越界
//size是最大长度-1
4. 二维字符数组
char str[3][4]={"","",""}; //有3个字符串,每个字符串占1行,每个字符串占4-1个字节
scanf("%s",str[i]);
五、函数
- 函数
程序编译以源程序文件为单位,不是以函数为单位进行编译的
程序执行从main函数开始,在main结束
1. 形参和实参
有参函数中,定义函数时函数名后面括号中的变量称“形参”,调用函数时,函数名后括号中称“实参”.
形参从未出现在函数调用时,并不占存储单元,调用结束后,所占内存单元也被释放.
实参可以是常量、变量、表达式,但必须有确定的值.
实参想形参传参是 值传递,单向传递
2. 函数递归
调用一个函数的过程中又直接或间接调用该函数本身
举例:p111,汉诺塔Hanoi
3. 数组元素作函数实参
实参可以是表达式,数组元素是表达式的组成部分,可作为实参,单向传递,值传递
举例:114,排序
4.数组名作函数参数
此时形参用数组名或指针变量
举例:115,求平均、选择法排序
5. 局部变量和全局变量
- 局部变量
在一个函数内部定义的变量是内部变量,只在本函数范围内有效。
主函数定义的变量只能在主函数中有效,不能使用其他函数中定义的变量
形参是局部变量
一个函数内部,复合语句中定义的变量只在符合语句中有效 - 全局变量
函数外定义的变量
全局变量在全部执行过程都占用存储单元,而不是仅在需要时才开辟单元
6.静态存储和动态存储方式
从变量值存在时间角度,分静态存储方式(系统分配固定存储空间)和动态存储方式(动态分配存储空间)
存储空间分三部分:程序区、静态存储区、动态存储区
四种存储方式:自动的(auto)、静态的(static)、寄存器的(register)、外部的(extern)
- auto(动态)
局部变量不声明static,都是动态分配存储空间,在调用函数时系统分配存储空间,调用结束自动释放存储空间,称自动变量,用auto声明(auto int b,c=3;) - static(静态)
局部变量的值在函数调用结束后不消失而保留原值,起占用的存储单元不释放,下一次调用时,变量已经有值,是上一次结束调用的值,称静态局部变量,用static声明 - register(包含静态和动态方式)
程序用到哪一个变量的值,由控制器发出指令将内存中该变量的值送到运算器中,为提高执行效率,允许将局部变量的值放到CPU寄存器中 - extern(静态)
外部定义的全局变量,可在程序中各个函数所引用,可在一个文件内声明外部变量,也可在多个文件声明外部变量。如果想只限于本文件引用,可定义外部变量时加一个static声明
7. 变量的定义和声明
定义:需要建立存储空间,如 int a
声明:不需要建立存储空间,如 extern a;
8. 内部函数和外部函数
- 内部函数
一个函数只能被本文件中其他函数调用,定义时在前面加static
如static int fun(int a,int b) - 外部函数
定义函数是,加上extern,可供其他函数调用,如果省略extern隐含为外部函数
调用外部函数,用extern对函数声明,表示该函数是在其他文件中定义的外部函数
6. 预处理
- 预处理命令
宏定义、文件包含、条件定义,这些命令以#开头,如#define 、#include - 宏定义
#define 标识符 字符串
#define PI 3.1415926
可以层层置换
带参数的宏定义:不是简单的字符串替换,还要进行参数替换
#define 宏名(参数表) 字符串
#define S(a,b) ab //Area=S(3,2) 结果为32=6 - 文件包含
指一个源文件可以将另一个源文件全部内容包含
被包含文件与其所在文件,编译后是同一个文件 - 条件编译
是对部分内容指定编译的条件,使其满足一定条件才进行编译
几种形式
#ifdef #ifndef #if
#else
#endif
六、指针
1. 指针
- 地址和指针
地址:内存区每个字节有一个编号
按变量地址存取变量值的方式称"直接访问",将变量的地址存到另一个变量中称"间接访问"
指针:一个变量的地址称该变量的指针
32位平台任何指针类型都是4字节 - 指针的定义和引用
定义步骤:定义变量类型,*修饰变量,建立关系
int num=10;
int p;
p=#
指针变量中只能存放地址
& 是取地址、是取值
举例:142
[]和()关系:[]是()的缩写
2. 数组指针
- 数组元素的指针就是数组元素的地址 举例:选择法排序,151
定义指向数组元素的指针:int a[10]; int *p; 赋值:p=&a[0]; - 引用数组元素:下标法:a[i] 指针法:(a+i)或(p+i)
数组名作函数参数:数组名是数组首元素地址,传递的值是地址,要求形参是指针变量
四种情况:形参和实参都用数组名、实参数组名形参指针变量、形参实参都用指针变量、实参指针变量形参数组名
指向同一数组的两个指针变量可以相减,可以相互赋值,不可以相加
int arr[5]={10,20,30,40};
int *p=&arr[0];
printf("%d\n",*p++); //10
printf("%d\n",(*p)++); //20
printf("%d\n",*(p++)); //21
- 本质是数组,只是数组每个元素是 指针
int num1=10,num2=20,num3=20;
int *arr[3]={&num1,&num2,&num3};
- 多维数组指针
int arr[3][4];
int (*p)[4];
p=arr; //p和arr等价的前提是 二维的列数4和指针4相等
int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}},首行首地址是2000
表达式 | 含义 |
---|---|
a | 二维数组名,指向a[0],即0行首地址 2000 |
a[0]、*(a+0)、*a | 0行0列元素地址 2000 |
a+1、&a[1] | 1行首地址 2008 |
a[1]、*(a+1) | 1行0列元素,a[1][0]地址 2008 |
a[1]+2、*(a+1)+2、&a[1][2] | 1行2列元素,a[1][2]地址 2012 |
(a[1]+2)、(*(a+1)+2)、a[1][2] | 1行2列元素a[1][2]的值 13 |
3. 指针作为函数的参数
想在函数内部修改外部变量的值,需要将外部变量的地址传给函数
void swap(int *a,int *b)
{
int tmp;
tmp=*a;
*a=*b;
*b=tmp;
}
//调用
swap(&date1,&date2)
4. 函数指针
本质是指针变量,保存函数的入口地址
int mul(int a,int b)
{
return a*b;
}
int cal(int a, int b, int (*fun)(int,int))
{
return fun(a,b);
}
void point8()
{
printf("%d\n",cal(20,10,mul));
}
5. 字符串与指针
字符数组存放字符串:char strint[]=“I love you”;
字符指针指向字符串:char * *string=“I love you”;
对字符串字符的存取可用:下标法、指针法
字符数组只能对各个元素赋值,不能用char str[14]; str=“I love you”;
字符指针变量,可以用char * *a; a=“I love you”; 赋给a的不是字符,而是第一个元素的地址
字符指针变量,赋初值,char *a=“I am” 等价于char * *a; a=“I am”;
字符数组,赋初值,char str[4]={“am”}; 不能等价char str[4]; str[]=“am”;
字符数组,编译时为它分配内存单元,有确定的地址,字符指针变量,如果未分配地址值,并未执行具体一个字符数据
指针变量的值是可以改变的
6. 字符串处理函数
以str开头的都是遇到\0自动结束
strcat(str1,str2); //把2接到1后面,结果放在1中
strcpy(str1,str2); //把2复制到1中,1长度不小于2,\0也要复制
strcmp(str1,str2); //比较1和2,自左到右比,直到遇到不同或\0结束,1=2值为0,1>(<)2值为正\负数
strlen(str); //字符串长度,不包括\0
strlwr(str); //将字符串中大写字母换成小写
strupr(str); //小写换大写
strchr(str1,ch); //查找字符串中,字符c出现的位置,返回c和后面的,失败返回null
strstr(str1,str2); //查找字符串,字符串str2出现的位置,返回str2和后面的,失败返回null
strtok(str," : "); //字符串切割函数,失败返回NULL
第一次切割,str是首元素地址
第2-n次切割,str执行NULL
atoi、atol、atof:将字符串转成int long float型
sscanf(“abcDeFABC”,%[a-z],buf); //提取a-z
举例:97页
7. const
const int *p; //const修饰的是*,而不是p,*p只读,*p可读可写
int * const p; //const修饰的是p,而不是*,*p可读可写,p只读
8. 动态内存申请
- malloc函数
void *malloc(int size);
成功:返回空间起始地址,失败:返回NULL
对于malloc申请的空间,内容不确定,一般要用memset情空
void test01()
{
int *addr=NULL;
addr=(int *)malloc(sizeof(int)); //前后类型都要是int,进行了强制类型转换
memset(addr, 0, sizeof(int));
free(addr); //释放空间
}
-
calloc函数
void *calloc(size_t nmemb, size_t size)
在堆中申请 nmemb块,每块size大小 -
realloc函数
void *realloc(void *s, int newsize)
追加,在s指向的内存上重新申请内存,新的内存大小是newsize,包含之前的,因为s是起始位置
七、结构体和共用体
1. 结构体
- 结构体定义
定义结构体类型时,没有分配控件,不能赋值
//先定义类型,再定义变量
struct stu
{
int num;
char name[32];
int age;
};
struct stu lucy;
//定义类型的同时定义变量
struct stu
{
int num;
char name[32];
int age;
}lucy;
//定义一次性结构体
struct
{
int num;
char name[32];
int age;
}lucy;
- 结构体调用
调用
左边是普通结构体变量:lucy.num,lucy.name,lucy.age
左边是地址:p->num,lp->name,p->age
把一个结构体赋值给另一个
// 方法1:逐个赋值
bob.num = lucy.num;
strcpy(bob.name,lucy.name);
bob.age = lucy.age;
// 方法2:相同类型的结构体变量,直接赋值
bob=lucy;
// 方法3:方法2的底层实现
memcpy(&bob,&lucy,sizeof(struct stu));
- 结构体数组
struct stu arr[5];
2. typedef
- typedef给类型起别名,步骤:
先用类型定义变量:int a;
用别名替换变量名:int INT32;
前面加typedef:typedef int INT32;
3. 共用体
- union
共用体空间由最大的决定,是最后一次赋值有效
4. 枚举
将变量的值一一列举出来,变量的值只限于列举出来的值的范围内
- 定义
enum 枚举名
{
枚举值表(也称枚举元素)
};
八、文件
1. 文件
- 文件存取过程
程序数据区(内存)----文件缓冲区(系统/程序)—文件(磁盘0
缓冲区目的:提供存取效率 - 磁盘文件的分类
物理上所有磁盘文件都是以字节为单位进行顺序存储
从用户或操作系统使用角度分为:
文本文件:基于字符编码的文件(ASCII、UNICODE等,可用记事本打开)
二进制文件:基于值编码的文件(把磁盘中的数据按在内存中的存储形式原样输出到磁盘上,提高效率,但不好打开)
C语言不能直接操作文件,要用库函数间接对文件进行操作 - 库函数操作文件的相关信息,不是文件数据
2. 定义文件指针
FILE *fp=NULL;
- 说明:
FILE是系统用typedef定义出来的有关文件信息的一种结构体类型
FILE结构体含有文件名、文件状态、文件当前位置等信息
一般操作文件前,要定义一个文件指针指向将要操作的文件
3. 文件打开和关闭
-
文件打开fopen()
FILE *fp = NULL;
fp=fopen(文件名,文件使用方式);
文件名:要操作文件的名字,可包含路径信息 -
文件使用方式:读、写、文本或二进制
r:只读方式打开,文件不存在返回NULL
w:只写方式,文件不存在创建,文件存在则清空内容
a:追加方式打开,文件不存在创建,存在在结尾追加
+:同时以读写打开,即rw
b:二进制文件
t:文本文件,默认是t,可省略
打开方式与文件的存储无关,与操作系统有关
fp文件指针,打开失败返回空 -
文件关闭fclose()
// 打开文件、关闭文件
FILE *fp_aaa = NULL;
fp_aaa=fopen("aaa.txt","r+");
if(fp_aaa==NULL)
printf("file open error");
fclose(fp_aaa);
4. 文件读写
- 顺序读写
字节读写函数:fgetc()、fputc()
字符串读写函数:fgets()、fputs()
数据库读写函数:fread()、fwrite()
格式化读写函数:fscanf()、fprintf() - 字节读写
ch=fgetc(fp);
文本文件:读到结尾返回EOF
二进制文件:读到结尾,使用fconf判断结尾
fputc(ch,fp);
输出失败,返回一个EOF
EOF是stdi0.h定义的常量,值为-1 - 字符串读写
fgets(str,n,fp);
从fp指向的文件中读入n-1个字符,在读入n-1个字符之前遇到换行符或EOF,读入结束,并读取换行符,在最后加一个’\0’,str为存放数据的首地址
读取成功返回字符串首元素地址
读取失败,返回NULL
fputs(“china”,fp);
第一个参数可以是字符串常量、字符数组名、字符指针
字符串末尾的’\0’不会写到文件中 - 数据块的读写
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
buffer:指向存储数据空间的首地址的指针
size:一次读写的数据块的大小
count:要读写的数据块个数
返回值:实际读写的数据块数
fwrite将内存的数据原样的输出到文件中,写入文件的数据不便于用户查看但不会影响程序读取 - 随机读写
rewind(fp);
复位文件流(将文件内部的位置指针移到文件首部)
ftell(fp);
获得文件流目前的读写位置
返回值:距离文件首部的字节数
fseek(fp,位移量,起始点);
移到文件流的读写位置(一般用于二进制文件)
位移量:以起始点为基点,向前、后移动的字节数
起始位置:
文件开头 SEEK_SET 0
文件当前位置 SEEK_CUR 1
文件末尾 SEEK_END 2 - 文件格式化操作
fprintf(文件指针,格式化字符串,输出表列);
fscanf(文件指针,格式化字符串,输入表列);
用fprintf和fscnaf对磁盘文件读写使用方便,但在输入时要将ASCII码转换为二进制形势,输出时将二进制形式转换为字符,花费时间较多。
在内存与磁盘频繁交换数据的情况下,最好用fread和fwrite函数
5. 文件结束和出错检测
- 文件结束检测
feof(fp);
文件未结束返回0,文件结束返回非0 - 读写文件出错
ferror(fp);
检查文件在用输入输出函数进行读写时是否出错(比如只读方式打开文件,却调用写函数)
返回0表示未出错,否则有错 - 文件出错标志和文件结束标志置0
clearerr(fp);
清除出错标志和文件结束标志,使他们值为0,无返回值
上一篇: 我的第一个python程序
下一篇: List遍历remove的那些事