C语言学习笔记(基础)

C语言

数据类型

变量名:必须由字母,数字下划线组成,区分大小写。变量名第一个字符必须是字母或下划线。注意不要与标准库内的变量或关键字重名,例如:this next x0等。
关键字:就是打出来高亮的字符,用来组成语言结构,例如return if for,有32个

整型or浮点型

整型变量用来存储整数,浮点型(float, double)用来存储小数

数据类型大小存储范围
int4字节 [ − 2 31 , 2 31 ) [-2^{31},2^{31}) [231,231)
unsigned int4字节 [ 0 , 2 32 ) [0,2^{32}) [0,232)
long long int8字节 [ − 2 63 , 2 63 ) [-2^{63}, 2^{63}) [263,263)
short int2字节 [ − 2 15 , 2 15 ) [-2^{15}, 2^{15}) [215,215)
char1字节 [ − 2 7 , 2 7 ) [-2^7, 2^7) [27,27)
unsigned char1字节 [ 0 , 2 8 ) [0, 2^8) [0,28)
float4字节精度为6位小数
double8字节精度位15位小数

输入输出函数:

int a; char ch;
scanf("%d%c", &a, &ch);
printf("%d%c\n", a, ch);

双引号里的"%d%c"为格式化字符,a为int类型对应%d,ch为char类型对应%c,更多的格式化字符如下:

数据类型格式
64为有符号整数 (long long)%lld
32为无符号整型 (usigned int)%u
八进制整数%o
十六进制数%x
单精度浮点数%f
双精度浮点数%lf

按以下样式输出
在这里插入图片描述

printf("Name    Age    Score\n");
printf("%-8s%-7d%.2f\n", "Li", 18, 92.5);
printf("%-8s%-7d%.2f\n", "Zhang", 20, 100.0);
printf("%-8s%-7d%.2f\n", "Xin", 17, 62.5);

%-5d表示先输出5个空格,然后让输出的数在这5个空格中左对齐。
%5d 表示右对齐。
%05d表示输出五位数,不够五位数在左边补0
%.2f表示保留两位小数,注意不是四舍五入,如果想四舍五入请用 math.h 中的round()函数

字符类型

ASCII码:由于计算机内存只能存储数据不能存储相应的符号,所以每个字符都会对应一个数字这就是编码方式,ASCII是C中字符存储的编码。一个char类型也相当于一个数字及其ASCII值,可以正常参与运算

for (char ch = 'a'; ch <= 'z'; ch++)
	... // 枚举26个字母
char a = 'C', b = a + 32;
// a中存的是大写字母,b中存的是其对应的小写字母
printf("%d\n", a + b);
// 将a和b的ASCII值相加并输出一个数

转义字符:有一些控制字符,你没法输入比如\a 响铃 \b退格
常用的有\n相当与按一次回车换行 \t相当于按一次Tab

字符串:用双引号括起来,最后有一个\0字符(不用手动加)表示一个字符串的末尾。
注意一定要让字符串以\0结尾否则不会正常输出
例如:"CHINA"在字符数组中的存储为 C H I N A \0
字符串常用的操作,会在后面讲到

顺序结构

赋值语句,注意与数学中等号的区别,左边是被赋值的变量。
输入两个变量并交换它们的值:

int a, b;
scanf("%d%d", &a, &b);
a = a + b;
b = a - b;
a = a - b;
printf("a = %d\n b = %d\n", a, b);

注意区分以下语句的不同:

double a = (double) 1 / 100
double a = 1.0 / 100
double a = (double)(1 / 100)

注意有一些语句本身也是有值的,像(a = b)这整个语句的值为b,类似于函数的返回值。
逗号,可以并列两个语句,逗号语句本身的值为最后一个语句的值

int a = 1, b = 2;
printf("%d", (a = (b = a + b)));
printf("%d"(a = 1, b = 2));

注意自增和自减运算符:
++a,表示当时就给a加1;
a++,表示当时a不变,直到当前语句执行完遇到分号时再给a加1

int a = 0, b = 0, c = 0;
a = ++b + c++; // 此时 a = 1, b = 1, c = 1
c += (++a, ++b + a--); // 此时 a = 1, b = 2, c = 5
printf("%d %d %d\n", a, b, c);

这里还需要掌握以下math.h库中的数学函数比如:
max(), min(), abs(), fabs(), pow(), exp(), sqrt() 等

分支结构

判断语句本身也是有值的,如果值为非0的数表示语句中的条件成立,0表示不成立。
注意为避免由于运算符优先级产生的问题,使用的时候每一个步运算都最好加一个括号:

int a = 2, b = 5, c = 3;
(a != b) < c; // 表达式为真
a != (b < c); // 表达式为真
a > (b > c) ; // 表达式为真

判断两个变量是否相等用 “==” 不要写成 “=”

逻辑运算符号: ‘||’,’&&'用来连接两个判断语句:
  ‘||’ 如果两边有一个语句为真,那么值为真;
  ‘&&’ 两边语句都为真,那么值位真;
注意短路运算:

int a = 1, b = 1;
(a || (b = 2));  // 注意||右边等号是赋值不是判断
printf("%d %d\n", a, b); 
// a最后的值为1,b最后的值为1

由于||语句左边为真,那么最后结果一定为真,所以就不会执行b=2就直接输出了,”&&“有左边为假,就不会再计算右边了。

if 语句下如果只有一条语句就不用加大括号,如果有多条语句必须加大括号
else if 分支可以看成上一个if的else与下一个if的结合:

if () {...}
else {
	if () {...} 
	else {...}
}
// 去掉第一个else 的大括号
if () {...}
else if () {...}
else {...}

以下程序包含了几个易错的地方。

int a = 1, b = 4;
if (a = 0) // a = 0这个表达式值为0,一定为假
	if (a < !b) a = 4; 
else a = 5; // 这个else默认和第二个if是一个整体,没加大括号引发的血案
printf("%d\n", a); 
// 最后a = 0

switch 语句,注意case实际上是一个入口,一但进入这个入口就要一直向下走,直到遇到出口(遇到break或者最后一条语句)才能出来

int a = 1;
switch(a) {
	case 1:
		printf("欢迎来到第一个入口\n");
	case 2:
		printf("欢迎来到第二个入口\n");
		break;
	case 3:
		printf("欢迎来到第三个入口\n");
		break;
	default:
		break;
}

程序输出:
在这里插入图片描述

循环结构

for 循环,如果想让一段程序重复执行10次:

int i;
for (i = 1; i <= 10; i++)
	A;

i代表代码块A第几次循环,for循环中有三条语句,其作用如下
i = 1 表示 循环变量的初始值,因为要从1开始循环
i <= 10 表示循环终止条件,因为最多不超过10次
i++ 表示循环变量枚举的步长,由于我们是连续从1到10步长为1,相当于每次i += 1

for 循环中的语句执行顺序
1,先执行 i = 1,给循环变量赋初值语句
2,判断 i <= 10是否成立,不成立退出循环
3,执行循环体代码A,如果A有多个语句需要用大括号括起来,一个语句可以省略大括号
4,改变循环变量 i++
5,跳回到第二步重复执行

注意:for循环中的三个语句不一定全都需要,有时根据功能可以省略语句,但不能省略分号,也就是说for中的两个分号一直都有。不管如何循环,一定要给for循环结束条件,否则就死循环。

while循环是for的简化版,我们不想知道循环次数,只知道某个条件不成立的时候就停止循环。
while(i <= 10) 相当于 for (;i <= 10;)

int i = 1;
while (i <= 10) {
	A;
	i++;
}

以上程序执行顺序与for循环完全相同,可以说是for循环的工作原理。

如果你需要这样一种功能,就是不管循环条件是否成立,你都需要先执行一次循环体,那么就要使用

int i = 1;
do {
	A;
	i++;
} while (i <= 10);

该结构的执行顺序:
1、循环变量i赋初值 i=1
2、执行循环体 A
3、修改循环变量 i++
4、判断条件 i<=10 不满足条件退出
5、跳回步骤2继续循环

强烈建议拿笔模拟循环进程,并且尝试交换一些语句的位置看看会发生什么情况。
循环变量i的出现是为了判断循环应当何时结束,有些情况可以没有循环变量,但是一定有终止条件。

如果想在循环时跳过一些语句,需要用到循环控制语句break和continue,以for循环为例:
输出1到10以内能够被三整除的数字

int i;
for (i = 1; i <= 10; i++) {
	if (i % 3 != 0) continue;
	printf("%d ", i); 
}

该循环体的执行流程:
1,先执行 i = 1,给循环变量赋初值语句
2,判断 i <= 10是否成立,不成立退出循环
3,执行if (i % 3 != 0),如果if条件成立 直接跳到步骤5,略过了循环体其他语句的执行。
4,执行printf("%d ", i);,能执行的这里的说明i是3的倍数,输出结果。
5,改变循环变量 i++
6,跳回到第二步重复执行

判断素数:

int i, n;
scanf("%d", &n);
for (i = 2; i * i <= n; i++) {
	if (n % i == 0) {
		puts("No"); 
		break;
	}
}
if (i * i > n) puts("Yes");

该循环体一但执行到break就会直接跳出循环,执行if(i*i > n)

双重循环:
如果想让你输出一个n行m列表格第i行每个单元格的坐标,这就循环枚举第i行每一列上的单元格就行:

int i, j;
scanf("%d", &i);
for (j = 1; j <= m; j++)
	print("(%d,%d)", i, j);

如果要对每一行都输出这一行每个单元格的坐标,可以发现这又是一段程序执行n次的情况,适合用for循环来实现,里面的循环体A就是上面的程序。

int i, j;
for (i = 1; i <= n; i++) {
	for (j = 1; j <= m; j++) 
		printf("(%d,%d)", i, j);
	printf("\n"); // 因为不同行之间都有一个换行
}

这样对于用二重循环输出图形的题目我们就只用去想每个坐标对应的单元格应当填什么。

输入n,输出一个由星号组成底边为n个‘*’的等腰三角形:保证n为奇数
Sample input:
5
Sample output:

  * 
 ***
*****

可以发现
在第一行中只用在对应的[1,2] [4,5] 区间中填空格,[3,3]中填*
在第二行中只用在对应的[1,1] [5,5] 区间中填空格,[2,4]中填*
在第三行中只用在对应的[1,0] [6, 5]区间中填空格,[1,5]中填*
带入n总结一下会发现:
在第i行我们只用在对应 [ 1 , n + 1 2 − i ] [ n + 1 2 + i , n ] [1, \frac{n+1}{2}-i][\frac{n+1}{2}+i, n] [1,2n+1i][2n+1+i,n]输出空格,在其它区域输出‘*’

int main() {
	int i, j, n;
	scanf("%d",&n);
	
	for (i = 1; i <= (n + 1) / 2; i++) {
		for (j = 1; j <= (n + 1) / 2 - i; j++) 
			printf(" ");
		for (int j = (n + 1) / 2 - i + 1; j <= (n + 1) / 2 + i - 1; j++) 
			printf("*");
		for (int j = (n + 1) / 2 + i; j <= n; j++) 
			printf(" "); // 可以不加这些空格直接换行效果一样
		printf("\n"); 
	}
}

输出乘法表也是一样的道理你只需要考虑一下每个单元格应该填入什么。

函数

概述:例如实现一个求组合数的程序,其中你会用到多次求阶乘运算,而每次求阶乘都需要一个for循环,这样虽然能够实现目标,但是代码量将十分巨大。因此我们可以将阶乘功能封装成一个函数:
类似于数学上的函数,给函数一个自变量,然后得到一个函数值,下面是阶乘函数的定义。

int factorial(int n) {
	int ans = 1;
	for (int i = 1; i <= n; i++) 
		ans *= i;
	return ans;
}

最前面的int的表示函数值类型,factorial表示函数名,后面的括号里面是自变量n,后面大括号里面是这个函数的表达式。可以理解为你给名叫factorial的人一个int类型的数n,然后他计算完之后把int类型的计算结果反馈给你,反馈就是这里的return,也叫做函数的返回值。
注意函数定义的时候一定要有括号和大括号。

使用函数之前需要声明函数,告诉编译器你要使用的函数,用如下语句申明:

int factorial(int);

写上函数值类型函数名称和参数类型,参数名和后面的函数体全部省略。

函数的使用

c = factorial(n) / factorial(m) / factorial(n - m);

上面实际上是完成了对C(n, m)的计算结果在变量c中(n和m不要给的太大)
函数使用时的注意事项:
1、函数自变量的传递,函数定义时自变量的个数和使用时传递的个数是相同的 (默认参数除外) ,并且通常的参数传递是拷贝的过程。就像上面说的,你给factorial一个n值,他对这个n值修改的时候你的n值是不会变得。要想改变你的n值,你需要告诉factorial你的n值写在什么地方,让他根据地址去找你的n值来使用,这样他改变n值的时候你的n值也会跟着改变。
下面是正确的swap实现方式,void表示无具体反馈值的函数(因为a和b值的交换就是函数的反馈)。int& a是取引用,也就是规定函数和你使用的是同一个a值。

void swap(int& a, int& b) {
	int t = b; b = a; a = t;
}

2、函数的返回值,注意return的值的类型必须和定义一致,如果没有返回值一定要用void定义和申明,函数一但执行到return语句就会立即结束,不会再执行下面的语句。
3、函数的递归,就是我调用我自己,这个过程的理解十分困难需要查阅资料。这里简单说一下:在数学中函数的表达式有递归式,例如阶乘:
f ( x ) = { x f ( x − 1 ) x > 1 1 x = 1 f(x) = \left\{ \begin{array}{c} xf(x-1) & x > 1 \\ 1 & x = 1 \end{array} \right. f(x)={xf(x1)1x>1x=1
如果能看懂数学表达式的话,函数的递归因该不难理解,下面给出代码:

int f(int n) {
	if (n == 1) 
		return 1;
	return n * f(n - 1);
}

还有求最大公因数的辗转相除法:(使用了三目运算符)

int gcd(int a, int b) {
	return b == 0 ? a : gcd(b, a % b);
}

主函数

主函数在控制台程序中定义为main函数,是程序入口,系统首先调用主函数来执行程序。目前主函数要求你必须定义为int类型并且当主函数成功结束后返回数值0。主函数的定义如下:

int main(int argc, char* argv[]) {
	...
	return 0;
}

其中,main函数也有参数,这些参数是系统调用的时候传给程序的。argc是系统传给程序参数的个数,后面*argv[]类似于一个二维的字符数组,里面存储的是参数内容。你可以尝试输出argv[0] (这是一个字符串),你会看到的程序当前在系统中的路径和你的程序名。

int main(int argc, char* argv[]) {
	printf("%s\n", argv[0]);
}

如果想要模拟系统调用程序的过程,需要在命令行中运行程序(windows下的cmd)在输入exe文件名后就可以跟上参数。

预编译命令

预编译命令就相当于在程序被逐行编译之前对代码做的处理工作。
#include 命令 用来包含头文件 头文件中含有很多已经定义的函数,处理该命令的时候,会把被包含的文件中所有程序都加到你代码的开头,和你的程序一起编译。
#define 宏定义命令,例如:

#define PI 3.14

编译前,会把你所有文件中的PI替换成3.14,对就是原封不动的替换掉。

#define H 1 + 1
int main() {
	printf("%d", H * H);
}

得到的结果会是3而不是4,因为相当于计算 1 + 1 * 1 + 1
当然宏定义也可以带参数例如:(使用了三目运算符)

#define Max(X,Y) ((X) > (Y) ? (X) : (Y))

这个宏定义是用来计算max的,和函数功能一样但原理不同,加括号是为了防止优先级问题,就是上一个程序的问题。

#ifdef 条件编译命令,是给编译器指定编译哪些程序。

#define DEBUG
#ifdef DEBUG
	...
#else
	...
#endif

这表示如果你宏定义了DEBUG这个宏变量那么编译器就会编译#ifdef到#else之间的程序。如果你注释掉#define DEBUG则编译器会编译#else和#endif之间的程序。注意,分支结构中的if和else是控制的代码执不执行,而这里的是控制的是编译不编译,执行的一定被编译过,编译过的不一定被执行过。

数组

数组应该是初学者第一个接触的数据结构,设想一下如果你要处理一系列的数据,你肯定不能给每一条数据都定义一个变量来储存,那么就需要用到数组了。

数组的初始化:

int a[] = {1, 2, 3, 4, 5}; // 中括号里不填数字,编译器会自动根据大小推断
int b[10] = {0}; // 初始化为0,长度为10,编号为0到9

注意中括号中所填的数必须是一个常量整数类型,比如说一个具体的数,或者 const int

访问数组中的元素,用a[i]来访问a的第 i + 1 i+1 i+1个元素(注意数组编号从0开始,i可以变量也可以是常量),其中i通常是一个循环变量,因为要批量操作数组中的每一个元素。

例:输入n个数,用数组储存并求和(求最大最小值平均值同理)

int i, sum = 0, n, a[1005];
scanf("%d", &n);
for (i = 1; i <= n; i++) 
	scanf("%d", &a[i]);
for (i = 1; i <= n; i++) 
	sum = sum + a[i];
printf("%d\n", sum);

求斐波那契数列第n项(输入不要太大,斐波那契数列增长速度极快)

int n, i, f[45] = {0, 1};
scanf("%d", &n);
for (i = 2; i <= n; i++) 
	f[i] = f[i - 1] + f[i - 2];
printf("%d\n", f[n]);

插入排序:先选出最大的,再选出剩下元素中最大的,依次进行下去,就排好序了。

int n, i, j, a[1005];
scanf("%d", &n);
for (i = 1; i <= n; i++) 
	scanf("%d", &a[i]);
	
for (i = 1; i <= n; i++) // 选出i到n最大的数交换到i的位置上 
{
	int p = 0;
	// 选出i到n中最大的数的位置
	for (j = i; j <= n; j++) 
		if (a[p] < a[j]) p = j;
	// i位置和p位置两个数做交换
	int t = a[p];
	a[p] = a[i];
	a[i] = t;
} 
	
for (i = 1; i <= n; i++) 
	printf("%d ", a[i]);

二维数组,可以类比为一张表格,以前说过二重循环可以输出表格中每个单元格的坐标,那么二重循环同样也可以用来枚举二维数组中的元素。
二维数组的初始化:

int a[1005][1005] = {0}; // 初始化为0
int a[][3] = {{1, 2, 3}{2, 3, 4}, {3, 4, 5}};

通过a[i][j]就能访问坐标为(i,j)单元格的数据。
例:实现矩阵乘法(矩阵乘法的定义可以百度)别被循环的数量吓着了:

int a[105][105] = {0}, b[105][105] = {0}, c[105][105] = {0};
int n, m, l, i, j, k;
// 输入n*m的矩阵a,输入m*l的矩阵b 
scanf("%d%d%d", &n, &m, &l);
for (i = 1; i <= n; i++) 
	for (j = 1; j <= m; j++) 
		scanf("%d", &a[i][j]);

for (i = 1; i <= m; i++) 
	for (j = 1; j <= l; j++) 
		scanf("%d", &b[i][j]); 
		
// 矩阵乘法
for (i = 1; i <= n; i++) 
	for (j = 1; j <= l; j++) 
		for (k = 1; k <= m; k++) 
			c[i][j] += a[i][k] * b[k][j];
	
for (i = 1; i <= n; i++) {
	for (j = 1; j <= l; j++) 
		printf("%d ", c[i][j]);
	putchar('\n');
}

字符数组

字符数组于数组结构类似,就是存储的元素由数字变成字符。字符数组是用来存储字符串的。
字符数组的初始化:

char str1[] = "abdcd";
char str2[] = {'a', 'b', 'd', 'c', 'd', '\0'};

上面两种初始化的效果是一样的,但是注意第二种初始化最后要加'\0',表示字符串的结束(第一种自动加),字符串的很多函数都依赖这个结束符工作。

字符串有很多特殊的地方。字符串的读入:

scanf("%s", str);
gets(str);

str必须是一个字符数组,同时不加’&‘符号,以上两种读入方式效果相同,都会自动加’\0’结束符。

字符串的输出

printf("%s", str);
puts(str);

上述两者效果相同,但后者会在最后加一个换行。

下面的函数都来自于<stirng.h>库使用的时候记得include否则会提示函数找不到
得到一个字符串长度:

len = strlen(str)

把字符串str1拷贝给字符串str2:

strcpy(str2, str1)

比较字符串大小,按照字典序,主要用来判断是否相等

strcmp(str1, str2)

如果str1和str2相等函数返回值是0,不能用str1 == str2来判断相等。

例:在字符串s中查找字符串p出现的次数

char s[1005], p[1005];
int i, j, slen, plen, cnt = 0;
scanf("%s%s", s, p);
slen = strlen(s);
plen = strlen(p);
for (i = 0; i + plen <= slen; i++) {
	// 从第i个位置与p字符串进行匹配 
	for (j = 0; j < plen; j++) 
		if (s[i + j] != p[j]) break;
	// 匹配成功找到一个 
	if (j == plen) cnt++;
}
printf("%d\n", cnt);

数组或字符数组作为函数参数

int sum(int s[], int len)

上述函数以数组s作为参数,注意这里数组s不是一个拷贝,修改s会改变调用时传入的数组,这是一个实参。

指针

指针是存储地址的变量,该内存地址中的数据被称为指针指向的元素。指针所占的内存空间是固定的(32位系统模式下4个字节),与它所指向元素所占的内存大小无关。
指针的定义:
指针在定义时需要指定其指向元素的类型。

int* p; // 指向整型变量的指针
float* fp; // 指向浮点类型的变量
char* cp; // 指向字符类型的变量

与地址有关的运算:
&x表示取变量x的地址;*p表示取指针p所指向的元素,是一个寻址过程(不要与指针定义时的星号记混)

int i, *p; // 这里的*号是定义时的星号,不是取p指向的元素
// 此时p的类型为(int*),而它指向元素的类型为(int)
p = &i; // 此时p储存的时变量i的地址,也可以叫做指针p指向变量i
*p = 1; // 此时*p就是i,也就是把i修改为1

注意指针p在定义时其指向的内容是是随机的,不要给其指向的元素赋值,这是非法的。

使用指针来实现swap函数:

void swap(int* a, int* b) {
	int t = *a;
	*a = *b;
	*b = t;
}
int main() {
	int a = 1, b = 2;
	swap(&a, &b);
}

指针与一维数组:
C语言规定数组名是表示数组首地址的地址常量,也就是常量指针。

int a[100];
*a = 5; //相当于把a[0]修改为5,因为数组名称a是一个指向数组首元素a[0]的一个常量指针。
// 但是你不能 a = &i 来改变a的值,因为它是常量

指针的加法运算:
指针的加法运算不是单纯的把地址加上一个数,而是使指针指向同类型的下一个元素 ,比如p+1,如果p是(int*)类型的指针,那么p+1的地址将加4,因为一个int占用四个字节。如果p是(char*)类型的指针,p+1的地址将加1,因为char占用一个字节。

输入n个数放到数组里:

int i, n, a[100];
scanf("%d", &n);
for (i = 1; i <= n; i++)
	scanf("%d", a + i); //实际上a + i等价于&a[i]

使用指针来访问数组:

int i, j, a[] = {0, 1, 2, 3, 4, 5};
int* p = a + 1;
for (i = 0; i < 3; i++) 
	printf("%d", p[i]);
	 // 实际上p也能看成一个数组 

以上会输出 1 2 3 p[i]等价于*(p + i),所以有时候你写i[p]也是可以的,但最好不要这样写。
再看数组做为函数传参:
数组传参其实只是把首元素的地址传给了函数

int sum(int s[], int len) {
	int i, ans = 0;
	for (i = 0; i < len; i++) 
		ans = ans * s[i];
	return ans; 
}

也可以这样写:

int sum(int* s, int len)

调用的时候可以:

sum(&a[0], len);
sum(a, len);

最后就来解释一下为什么读入字符串的时候不加&符号,因为字符串变量名相当于一个字符数组的首地址,它本身就是地址,所以不用加&符号取地址了。
至于二维数组的指针,其实二维数组名称是一个指向指针的指针,类型为(int**),它指向的其实是一个数组。过多的内容这里就不深入介绍了。

结构体

整型和字符型是远远不能满足我们的需要,因为在实际问题中,一个元素比如日期的存储,需要三个数据来描述它,年份,月份,天数。这样通过定义结构体我们能够将三个数据封装成一个含有特定意义的类型,日期类型。
结构体的定义:

struct Date{
	int year;
	int month;
	int day;
};

注意结构体定义相当于告诉编译器这个结构有什么东西,但它不是真是存在于内存中的,需要用它来定义变量(实例化),就跟最常用的int一样。
结构体实例化:

struct Date{
	int year;
	int month;
	int day;
}date1;
Date date2, *p = &date1;

直接在定义的大括号后面加定义的变量,或者采用类型名加变量名的定义方法都是可行的。现在对于每个变量date1, date2都有相互独立的三个数据(结构体成员)year,month,day。当然也可以定义指向一个Date类型数据的指针。

结构体成员的访问:

// 通过变量date2来访问
date2.year = 2020; 
date2.month = 12;
date2.day = 22;
// 通过指向结构体变量的指针来访问
p->year = 2020;
p->month = 12;
p->day = 22;

通过运算符号’.‘访问变量对应的成员,’->'来访问指针对应的成员。注意由于还没有定义结构体之间的运算方法,通常只能赋值date1 = data2,而没有相乘相加。
例:输入姓名和其对应的分数实现按分数排序

struct Data{
	int score;
	char name[55];
};
Data stu[1005]; // 结构体数组
 
int main() {
	int n, i, j; 
	scanf("%d", &n);
	for (i = 1; i <= n; i++) 
		scanf("%s%d", stu[i].name, &stu[i].score);
	
	// 冒泡排序 
	for (i = 1; i <= n; i++) 
		for (j = n; j > i; j--) 
			if (stu[j].score > stu[j - 1].score) {
				Data t = stu[j];
				stu[j] = stu[j - 1];
				stu[j - 1] = t;
			}
	
	for (i = 1; i <= n; i++) 
		printf("%s %d\n", stu[i].name, stu[i].score);

	return 0;
} 

文件

需要掌握基本文件操作方法。由于在处理数据的时候一般不是逐条把数据输入到那个黑框框里而是从文件中读取数据。

文件是stdio.h中定义的数据类型,名称为FILE,一般都用它来定义文件指针。fopen是打开文件的函数,fclose是关闭文件的函数,这两个必须成对使用。

FILE* fp;
fp = fopen("MyProgram/data.txt", "rb"); // 路径也可以写成"MyProgram\\data.txt"
if (fp == NULL) {
	// 打开文件后一定要判断一下文件指针是否为NULL,为NULL就说明打开失败不能继续操作下去,这是一个好习惯就像你在做除法先判断除数是否为0。 (NULL是一个宏变量在C语言中其值为0)
	printf("Open Error!!\n");
	return 0;
} 
fclose(fp);

注意,这里在fopen 中填入的第一个变量是文件路径,注意这是相对路径,及程序从源文件所在的目录开始找。"r"表示以只读方式打开文件。”w”表示以只写的方式打开文件,这种方式打开文件文件会被清空。如果想在文件之后再添加内容需要“a”表示追加数据。具体如下表。
在这里插入图片描述
文件读写操作:

#include <stdio.h>
struct Student{
	char name[10];
	int score;
}stu[3] = {{"aaa", 100}, {"bbb", 68}, {"ccc", 85}}; // 现在已经不建议这么写了,这样是为了方便 
int main() {
	FILE *fp;
    struct student stu_copy[3];
    int i;
    fp = fopen("data.dat","wb+");
    // 向文件中写入3个student变量
    fwrite(stu, sizeof(student), 3, fp);
    rewind(fp);
    // 从文件中读入3个student变量 
    fread(&stu_copy, sizeof(student), 3, fp);
    for(i = 0; i < 3; i++)
         printf("%s\t%d\n", stu_copy[i].name, stu_copy[i].score);
    fclose(fp);
    return 0;
}

fwrite的第一个参数表示写入数据的首地址,第二个表示一次写多少个字节的数据(sizeof()函数是用来查看某个变量所占字节数的,使用时sizeof(变量类型)),后面的数字表示写几次,fp是写入文件的文件指针,fread同理。
解释一下文件重定位rewind函数。这就像文本编辑器的光标一样,你写入一个或读入一个字符光标会向后移动,如果你想在开头读入东西,你就需要把光标移动到开头。如果你想把光标向前移动固定的几位。就需要fseek函数了。
常用的还有fprintf,fscanf也是向文件写入或读入。

// 这里的fp通常时txt文件时,用下面的写法。
fprintf(fp, "%d", 10); // 向文件中输出一个数
fscanf(fp, "%d", &a); // 从文件中读入一个数并赋值给变量A

用法与printf和scanf几乎一样,这里就不再赘述了。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值