C语言
数组
定义一维数组
类型符 数组名[常量表达式];
int a[10];
int b[5] = {1, 2, 3, 4, 5};
void func(int n)
{
int a[2 * n];
}
指定数组为静态static
存储方式,则不能用“可变长数组”
static int a[2 * n]; // 不合法,a数组指定为`static`存储方式
指针
存储地址的变量:类型+地址
类型决定了指针加1时,地址移动多少个字节,例如:
int *p, a = 10;
p = &a;
p = p + 1; // 移动4个字节,因为一个正数在内存中占4个字节
指针+1,是加上一个元素所占用的字节数
定义指针
int *p1, *p2, a, b;
引用指针
- & 取址运算符
- * 指针运算符,或间接访问运算符,
*p
代表指针变量p
指向的对象。
int a = 100, b = 10;
int *p1, *p2;
p1 = &a;
p2 = &b;
指针引用数组
int a[5] = {1, 2, 3, 4, 5};
int *p;
p = &a[0]; // p的值是a[0]的地址
int a[5] = {1, 2, 3, 4, 5};
int *p;
p = a; // p的值是数组首元素(即a[0])的地址
数组引用的三种方法
-
下标法
#inlcude <stdio.h> int main() { int a[10]; int i; printf("please enter 10 integer numbers: "); for(i=0;i<10;i++) { scanf("%d", &a[i]); } for(i=0;i<10;i++) { printf("%d", a[i]); } printf("\n"); return 0; }
-
通过数组名计算元素地址,找出元素的值
#inlcude <stdio.h> int main() { int a[10]; int i; printf("please enter 10 integer numbers: "); for(i=0;i<10;i++) { scanf("%d", &a[i]); } for(i=0;i<10;i++) { printf("%d", *(a+i)); } printf("\n"); return 0; }
-
用指针变量指向数组元素
#inlcude <stdio.h> int main() { int a[10]; int *p, i; printf("please enter 10 integer numbers: "); for(i=0;i<10;i++) { scanf("%d", &a[i]); } for(p=a;p<(a+10);p++) { printf("%d", *p); } printf("\n"); return 0; }
方法1、2效率相同,C编译系统是将a[i]
转换为*(a+1)
处理的,即先计算元素地址,因此方法1、2找数组元素费时较多。第3种方法快于1、2方法,用指针变量直接指向元素,不用每次都重新计算地址,像p++
这样的自加操作是比较快的。有规律地改变地址值能大大提高执行效率。不过方法1、2,能更直观判断处理到第几个元素了,而第3种方法则不那么直观。
指针,数组作为形参
void func(int a[]);
void func(int *p);
C编译都是将形参数组作为指针变量来处理的
指针引用字符串
char string[] = "I love China";
char *string = "I love China";
指向函数的指针
编译系统为函数代码分配一段存储空间,这段存储空间的起始地址(又称入口地址)称为这个函数的指针。
类型名 (*指针变量名)(函数参数列表);
int (*p) (int, int);
用函数指针变量调用函数
#include <stdio.h>
int main()
{
int max(int, int);
int (*p) (int, int);
int a, b, c;
p = max;
printf("please enter a and b: ");
scanf("%d,%d", &a, &b);
c = (*p)(a, b);
return 0;
}
动态为函数指针绑定结构相同(返回类型,参数个数,每个参数的类型相同)的函数
#include <stdio.h>
int main()
{
int max(int, int);
int min(int x, int y);
int (*p) (int, int);
int a, b, c, n;
printf("please enter a and b: ");
scanf("%d,%d", &a, &b);
printf("please choose 1 or 2: ");
scanf("%d", &n);
if (n == 1) p = max;
else p = min;
c = (*p)(a, b);
return 0;
}
用指向函数的指针作为函数参数
void fun(int (*x1)(int), int (*x2)(int, int))
{
int a, b, i = 3, j = 5;
a = (*x1)(i);
b = (*x2)(i, j);
}
一个完整的例子:
#include <stdio.h>
int main()
{
int fun(int x, int y, int (*p)(int, int));
int max(int, int);
int min(int, int);
int add(int, int);
int a = 34, b = -21, n;
printf("please choose 1, 2 or 3: ");
scanf("%d", &n);
if (n == 1) fun(a, b, max);
else if (n == 2) fun(a, b, min);
else if (n == 3) fun(a, b, add);
return 0;
}
in fun(int x, int y, int (*p)(int, int))
{
int result;
result = (*p)(x, y);
printf("%d\n", result);
}
int max(int x, int y)
{
int z;
if (x > y) z = x;
else z = y;
return z;
}
int min(int x, int y)
{
int z;
if (x > y) z = y;
else z = x;
return z;
}
int add(int x, int y)
{
return x + y;
}
返回指针值的函数
类型名 *函数名(参数列表)
float *search((*p)[4], int n);
动态内存分配
void *malloc(unsigned size);
void *calloc(unsigned n, unsigned int size);
void free(void *p);
void *realloc(void *p, unsigned int size);
void指针
可以指向任意类型的指针
int a = 3;
int *p1 = &a;
char *p2;
void *p3;
p3 = (void *)p1;
p2 = (char *)p3;
p3 = &a;
printf("%d", *p3); // p3得到纯地址,但并不指向a,不能通过*p3输出a的值
结构体
定义使用结构体
struct 结构体名
{
成员列表
} 变量名列表;
例:
struct Student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
} student1, student2;
初始化和引用:
struct Student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
} student1={101, "Li Lin", 'M', 8, 0.1, "123 Nanhu Road"};
某一成员初始化:
struct Student b = {.name="Zhang"};
引用和可进行的操作:
student1.num = 10001;
student1.num;
student1.birthday.month;
student1.age++;
sum = student1.score + student2.score;
student1 = student2;
scanf("%d",&student1.num); // 输入&student1.num的值
printf("%o", &student1); // 结构体的首地址
结构体数据和结构体指针
结构体数组
struct Votes
{
char name[20];
int count;
} leader[3] = {"A", 10, "B", 8, "C", 5};
struct Votes
{
char name[20];
int count;
};
struct Votes leader[3];
struct Votes leader[3] = {"A", 10, "B", 8, "C", 5};
结构体指针
struct Votes v;
struct Votes *p;
p = &v;
(*p).count = 1000;
p->count = 1000;
指向运算符:
->
,p->count
等价于(*p).count
指向结构体数组的指针
struct Votes *p;
struct Votes leader[3] = {"A", 10, "B", 8, "C", 5};
for (p = leader;p<(leader+3);p++)
{
printf("%s: %d", p->name, p->count);
}
共同体(联合体)
所有成员从同一起始地址开始存储。
union 共同体名
{
成员列表;
} 变量列表;
特点:
-
在某一瞬间,只能存放其中一个成员的值,而不能同时存放多个。
-
可以初始化共同体,但只能有一个变量
union Data { int i; char ch; float f; } d = {1, 'a', 1.5}; // 错误,不能初始化3个成员,因为它们占用同一块存储单元 union Data d = {16}; // 初始化第一个成员 union Data d = {.ch = 'j'}; // 初始化指定的成员
-
起作用的成员是最后一次被赋值的成员
枚举(enumeration)
声明和使用枚举类型
enum [枚举名] {枚举元素列表};
enum Weekday
{
sun, mon, tue, wed, thu, fri, sat
};
然后可以用此类型来定义枚举类型变量:
enum Weekday workday, weekend;
// 他们的直只能来自枚举类型定义中的枚举常量(枚举元素)之一:sun, mon, tue, wed, thu, fri, sat
workday = mon; // 正确
weekend = sum; // 正确
workday = monday; // 错误,monday不是枚举类型Weekday的枚举常量(枚举元素)
也可以不声明有名字的枚举类型,而直接定义枚举变量:
enum {sun, mon, tue, wed, thu, fri, sat} workday, weekend;
性质:
-
枚举常量不能赋值
-
每个枚举元素都代表一个整数,C语言按照定义时的顺序默认它们的值为0,1,2,3…
指定元素值的定义:
enum Weekday { sun=7, mon=1, tue, wed, thu, fri, sat // sun为7,mon为1,后依序加1,sat为6 } workday, weekend;
-
枚举元素可以比较大小
if (workday == mon) ; if (workday > sun) ;
使用typedef
声明新类型
-
简单地用一个新的类型名代替原有类型名
typedef int Integer; typedef float Real;
-
用简单的类型名代替复杂的类型表示方法
(1). 新类型名代表结构体类型
typedef struct { int month; int day; int year } Date; Date birthday; // 不能再写成struct DateStruct birthday; Date *p;
(2). 新类型名代表数组类型
typedef int Num[100]; // 声明Num为数组类型名 Num a; // a为100个元素大小的整数数组
(3). 新类型名代表指针类型
typedef char * String; String p, s[10]; // 定义p为字符指针变量,s为字符指针数组
(4). 新类型名代表指向函数的指针
typedef int (* Pointer)(); // 声明Pointer为指向函数的指针类型,该函数返回整型值 Pointer p1, p2; // p1, p2为Pointer类型的指针
文件操作
ANSI C标准采用”缓冲文件系统“处理数据文件,自动在内存中为程序中每个文件开辟一个文件缓冲区。内存到硬盘输出必须先送到内存缓冲区,内存缓冲区装满后写入硬盘,从硬盘到内存,一次从硬盘读取文件的的一部分到内存缓冲区(写满),然后从缓冲区逐个地将数据送到程序数据区(给程序变量)。
文件类型指针
由一个结构体保存文件信息:文件名,文件状态,大小,当前位置等。一种C
编译环境提供的stdio.h
有以下文件类型声明:
typedef struct
{
short level; // 缓冲区“满”或“空”的程度
unsigned flags; // 文件状态标志
char fd; // 文件描述符
unsigned char hold; // 如缓冲区无内容不读取字符
short bsize; // 缓冲区的大小
unsigned char * buffer; // 数据缓冲区的位置
unsigned char * curp; // 指针当前的指向
unsigned istemp; // 临时文件指示器
short token; // 用于有效性检查
} FILE;
打开关闭文件
#include <stdio.h>
FILE *fp;
fp = fopen("filename", "r");
文件打开模式:
模式字符 | 含义 | 如果文件不存在 |
---|---|---|
“r” | 读取 | 出错 |
“w” | 写入 | 新建文件 |
“a” | 追加 | 出错 |
“rb” | 二进制读取 | 出错 |
“wb” | 二进制写入 | 新建文件 |
“ab” | 二进制追加 | 出错 |
“r+” | 读写 | 出错 |
“w+” | 读写 | 新建文件 |
“a+” | 读追加 | 出错 |
“rb+” | 二进制读写 | 出错 |
“wb+” | 二进制读写 | 新建文件 |
“ab+” | 二进制读追加 | 出错 |
fclose(fp);
顺序读写数据文件
读写单个字符:fgetc
,fputc
char fgetc(FILE *fp);
int fputc(char ch, FILE *fp)
读写字符串: fgets
, fputs
char * fgets(char *str, int n, FILE *fp); // 读取长度为n-1的字符串,存放到str里面
int fputs(char *str, FILE *fp)
格式化读写
fprintf(FILE *fp, char *fmt_str,...);
fscanf(FILE *fp, char *fmt_str,...);
fprintf(fp, "%d,%6.2f", i, f);
fscanf(fp, "%d,%6.2f", &i, &f);
输入时需要将文件中的编码字符转换为二进制形式再保存在变量中;输出时又需要将二进制形式转换成编码字符。转换操作需要耗费较多时间,因此对于内存与硬盘频繁交换数据的场景,尽量避免使用fprintf
和fscanf
函数。
二进制读写
fread(buffer, size, count, fp);
fwrite(buffer, size, count, fp);
buffer: 地址,存放从文件读入的数据
size: 数据项大小(字节数),通常通过sizeof()
函数得到
count: 要读写多少个数据项(每个数据项的长度为size)
fp: FILE指针
随机读写
rewind(FILE *fp); // 返回到文件头
fseek(FILE *fp, int count, int start) // 偏移位置
ftell(FILE *fp); // 文件当前位置
起始点 | 变量名 | 数字 |
---|---|---|
文件开始位置 | SEEK_SET | 0 |
文件当前位置 | SEEK_CUR | 1 |
文件结尾位置 | SEEK_END | 2 |
文件读写错误检测
ferror(FILE *fp);
clearerr(FILE *fp);