1.赋值运算符相关
赋值运算符,若有a=b=c,按从右到左的顺序,先把c的值赋给b,再把b的值赋给a(即=的结合性是从右向左的)。整个赋值表达式的值为被赋予的值。
2.强制类型转换的实质
例如:float x=3.14;
int a=(int)x;
进行强制类型转换时(int)x得到一个int类型的临时值,它等于x的整数部分,将其赋给a后该临时值就不存在了。变量x在该过程中不受影响。
3.C语句相关
一个表达式的在最后加一个分号就构成一个语句。分号是一个语句不可缺少的组成部分,不是两个语句间的分隔符。
4.浮点数精度
在使用%f输出浮点数时,float类型的数据的存储单元只能保证6位有效数字。double型数据能保证15位有效数字。
4.输入函数相关
1.scanf()函数读入字符或字符串时,无法过滤空格、换行符。例如:
scanf("%c%c%c",&c1,&c2,&c3);
键盘输入:abc 变量的值: c1='a',c2='b',c3='c';
键盘输入: a b c 变量的值:c1='a',c2=' ',c3='b'; 这样输入scanf()要改成:scanf("%c %c %c",&c1,&c2,&c3);即对应位置也要加空格。
2.scanf()函数读入数值型数据时,如输入空格、回车、Tab键或遇见非法字符(即不属于数值类型),则认为该数据结束。
例如:scanf("%d%d%d",&x1,&x2,&x3);
输入:1235 4a6 变量的值:x1=1235,x2=4,x3=6;
注意:字符类型也属于整数类型,故将一个字符赋给字符变量和将该字符的ASCII码值赋给字符变量作用一致。
在键盘上输入信息时。字符会先暂存到键盘的缓冲器里,只有按了回车才会把这些字符一起送到计算机中,并按先后顺序分别赋给相应变量。(主要是为了缓和CPU和外设的速度差异,提升CPU的利用率)。
5.关系运算符和逻辑运算符相关。
1.关系运算符:
1. | < | 优先级相同(高) |
2. | <= | |
3. | > | |
4. | >= | |
5. | == | 优先级相同(低) |
6. | != |
前四种的优先级高于后两种。
关系运算符的优先级低于算术运算符高于赋值运算符。
2.逻辑运算符
- && 含义:逻辑与,如果表达式中一个为假,就不计算另一个。(短路)
- || 含义:逻辑或,如果表达式中一个为真,就不计算另一个。(短路)
- ! 含义:逻辑非。
6.条件表达式和逗号表达式
条件表达式的一般形式:
表达式1?表达式2:表达式3 注:条件运算符由?和:组成,需一起使用
若表达式1为真,条件表达式取表达式2的值,否则取表达式3的值。且另一个表达式不会进行计算。
例如:max=(a>b)?a:b; 等价于取a和b中的较大者
逗号表达式的一般形式:
expression1, expression2, ..., expressionN
逗号表达式的求值过程是从左到右依次计算每个子表达式,并返回最后一个子表达式的值作为整个逗号表达式的值。常用于for循环中。
7.二维数组有关指针
a | 二维数组名,指向一维数组a[0],即第0行的起始地址 | 2000 |
a[0],*(a+0),*(a) | 0行0列元素地址 | 2000 |
a+1,&a[1] | 1行起始地址 | 2016 |
a[1],*(a+1) | 1行0列元素a[1][0]的地址 | 2016 |
a[1]+2,*(a+1)+2,&a[1][2] | 1行2列元素a[1][2]的地址 | 2024 |
*(a[1]+2),*(*(a+1)+2),a[1][2] | 1行2列元素a[1][2]的值 | 13 |
8.C语言[ ]运算符
在C语言中,[]
运算符主要用于数组和指针的索引。它允许你访问数组或指针中的特定元素。
数组索引
当你有一个数组时,你可以使用 []
运算符来访问数组中的特定元素。数组索引是从0开始的,所以第一个元素的索引是0,第二个元素的索引是1,依此类推。
例如:
int arr[5] = {1, 2, 3, 4, 5};
int firstElement = arr[0]; // firstElement 现在是 1
int thirdElement = arr[2]; // thirdElement 现在是 3
指针索引
当你有一个指针,并且该指针指向一个数组或一块连续的内存时,你也可以使用 []
运算符来访问这块内存中的特定元素。
例如:
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;
int firstElement = ptr[0]; // firstElement 现在是 1
int thirdElement = ptr[2]; // thirdElement 现在是 3
注意
- 使用
[]
运算符时,要确保你访问的索引是有效的,否则可能会导致未定义的行为,如访问数组越界。 - 对于指针,要确保指针不为NULL,并且指向了足够大的内存块来容纳你要访问的元素。
数组和指针的区别
虽然数组和指针在某些情况下可以互换使用,但它们在C语言中有一些根本的区别。
- 数组是一个具有固定大小的数据结构,而指针是一个变量,它存储了一个内存地址。
- 数组的名字(在没有解引用的情况下)可以被当作一个指向数组第一个元素的指针。
- 当你传递一个数组到函数中时,实际上传递的是一个指向数组第一个元素的指针。
9.数组传递到函数中到底传了啥
在C语言中,当你将一个数组作为参数传递给一个函数时,实际上传递的是数组的首地址(即数组中“第一个元素”的地址),而不是整个数组的内容。这是因为数组在内存中是连续的,而数组名(在没有解引用的情况下)在大多数上下文中会退化为指向数组第一个元素的指针。
这意味着,当你将一个数组传递给函数时,函数会接收到一个指向数组第一个元素的指针,而不是数组的副本。因此,函数内部对数组的任何修改都会影响到原始数组,因为它们是共享同一块内存的。
在C语言中,当你将二维数组作为参数传递给函数时,实际传递的是数组的首地址,也就是指向数组第一行的指针。这个指针包含了数组第一行的内存地址。因为二维数组在内存中也是连续存储的,所以通过这个指针,函数可以访问整个二维数组。
重要的是要理解,传递的是指向第一行的指针,而不是整个二维数组的内容。同时,传递的指针是常量指针,意味着你不能在函数内部改变它指向的地址,但你可以通过这个指针来修改数组元素的值。
10.指向数组的指针
int arr[]={1,2,3};
int (*parr)[]=&arr;
注意:
parr
不等价于 arr
。
parr
是一个指针,它指向一个整型数组。具体来说,parr
是一个指向包含3个整数的数组的指针。当我们写 int (*parr)[] = &arr;
时,我们是在将 parr
初始化为指向 arr
的指针。这意味着 parr
存储的是 arr
的地址,即数组首元素的地址。
arr
,另一方面,是一个数组名。在大多数情况下,数组名会被解释为一个指向数组首元素的指针。但是,arr
和 &arr
是不同的:
arr
(或arr[0]
)是一个指向数组首元素的指针,类型是int*
。&arr
是一个指向整个数组的指针,类型是int (*)[3]
。
因此,parr
是一个指针,它存储了 &arr
的值,也就是整个数组 arr
的地址。而 arr
是一个数组名,它本身不是一个指针,但在大多数情况下可以当作指向数组首元素的指针来使用。
总结一下:
parr
是一个指针,它指向整个数组arr
。arr
是一个数组名,它表示数组本身,但在大多数情况下可以当作指向数组首元素的指针来使用。
所以,parr
和 arr
不是等价的。它们的类型不同,尽管它们指向相同的内存位置。你可以通过 parr
来访问和修改 arr
中的元素,但你不能将 parr
当作数组来使用(例如,你不能对 parr
进行数组索引操作 parr[i]
,而应该使用 (*parr)[i]
)。
11.指向指针数组的指针
注意:运算符[ ]的优先级要高于*
#include <stdio.h>
int main() {
// 创建一个包含三个指向整型的指针的数组
int a = 10, b = 20, c = 30;
int *ptr_array[3] = { &a, &b, &c };
// 创建一个指向这个指针数组的指针
int *(*parray_ptr)[3] = &ptr_array;
// 使用指向指针数组的指针来访问和打印原始数组的值
for (int i = 0; i < 3; ++i) {
printf("%d ", *(*parray_ptr)[i]);
}
return 0;
}
在这个例子中,parray_ptr
是一个指向指针数组 ptr_array
的指针。通过 *parray_ptr
,我们可以访问 ptr_array数组
,然后使用数组索引 [i]
来获取 ptr_array
中的第 i
个元素(指针),最后使用 *
来解引用这个指针并获取它所指向的值。
12.const和指针
const
是C语言中的一个关键字,它用于限定一个变量或对象不允许被改变,从而产生静态作用。当const
用于修饰指针时,它有两种常见的用法:指向常量的指针和常量指针。
指向常量的指针
指向常量的指针是指该指针指向的值不能被修改,但这并不意味着指针本身不能被修改以指向其他地址。声明这种指针的语法是const 数据类型 *指针名称
。例如:
const int *p;
这里,p
是一个指向const int
的指针,意味着你不能通过p
来修改它所指向的整数值,但你可以改变p
以指向另一个const int
。
常量指针
常量指针是指一旦指针被初始化指向某个地址,该指针本身就不能再被修改以指向其他地址。这种指针的声明语法是数据类型 *const 指针名称
。例如:
int *const ptr = &x;
在这个例子中,ptr
是一个指向int
的常量指针。一旦ptr
被初始化为指向x
的地址,ptr
就不能再指向其他地址了,但是你可以通过ptr
来修改x
的值(除非x
本身也是一个常量)。
13.使用文件的方式
根据数据的组织形式,把数据文件可分为ASCII文件和二进制文件。数据在内存中是以二进制形式存储的,如果不加转换地输出到外存,就是二进制文件,可以认为它就是存储在内存中的数据的映像,故也叫映像文件。如果要求在外存上以ASCII代码形式存储,则需要在存储前进行转换。ASCII文件又称为文本文件,每个字节存放一个字符的ASCII代码。
文件的使用方式 | 含义 | 如果指定的文件不存在 |
r(只读) | 为了输入数据,打开一个已存在的文本文件 | 出错 |
w(只写) | 为了输出数据,打开一个文本文件 | 建立新文件 |
a(追加) | 向文本文件尾部添加数据 | 出错 |
rb(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
wb(只写) | 为了输出数据,打开一个二进制文件 | 建立新文件 |
ab(追加) | 向二进制文件尾添加数据 | 出错 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,建立一个新的文本文件 | 建立新文件 |
“a+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“rb+”(读写) | 为了读和写,打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,建立一个二进制文件 | 建立新文件 |
“ab+”(读写) | 为了读和写,打开一个二进制文件 | 出错 |
14.文件操作相关函数
1.打开数据文件
fopen(文件路径,使用文件的方式);
FILE *fp;//定义一个指向文件指针的变量
fp=fopen("a1","r");//将fopen函数的返回值赋给指针变量fp
2.关闭数据文件
fclose(文件指针);
fclose(fp);
3.顺序读写数据文件
函数名 | 调用形式 | 功能 | 返回值 |
fgetc | fgetc(fp) | 从fp指向的文件读取一个字符 | 读成功。带回所读的字符,失败则返回文件结束标志EOF(即-1); |
fputc | fputc(ch,fp) | 把字符ch写入到文件指针fp所指向的文件中 | 输出成功,返回值就是输出的字符;输出失败,则返回EOF(即-1); |
#include <stdio.h>
int main() {
FILE *source_file, *destination_file;
char ch;
// 用只读模式打开文件source.txt
source_file = fopen("source.txt", "r");
if (source_file == NULL) {
perror("Error opening source file");
return 1;
}
// 用覆盖写模式打开destination.txt
destination_file = fopen("destination.txt", "w");
if (destination_file == NULL) {
perror("Error opening destination file");
fclose(source_file);
return 1;
}
// 将source.txt中的内容复制到destination.txt中
while ((ch = fgetc(source_file)) != EOF) {
fputc(ch, destination_file);
}
// 关闭文件
fclose(source_file);
fclose(destination_file);
printf("File copied successfully.\n");
return 0;
}
函数名 | 调用形式 | 功能 | 返回值 |
fgets | fgets(str,n,fp) | 从fp指向的文件读入一个长度为(n-1)的字符串,存放到字符数组str中 | 读成功,返回地址str,失败则返回NULL |
fputs | fputs(str,fp) | 把str所指向的字符串写入到文件指针变量fp指向的文件中 | 输出成功,返回0;否则返回非0值。 |
4.格式化方式读写文本文件
fprintf(文件指针,格式化字符串,输出表列);
fscanf(文件指针,格式化字符串,输入表列);
例如:
fscanf()
函数返回成功读取并赋值的输入项数。如果到达文件末尾或在读取任何输入项之前遇到输入失败,它将返回 EOF。
#include <stdio.h>
int main() {
int i = 3;
float f = 4.5;
FILE* fp = fopen("output.txt", "w");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
fprintf(fp, "%d,%f", i, f);
fclose(fp); // 关闭文件以确保数据被写入磁盘
// 现在可以尝试从 output.txt 文件中读取数据
int j = 0;
float k = 0;
fp = fopen("output.txt", "r");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
if (fscanf(fp, "%d,%f", &j, &k) == 2) {
printf("j 的值是: %d\n", j);
printf("k 的值是: %f\n", k);
}
else {
fprintf(stderr, "未能从文件中读取数据\n");
}
fclose(fp); // 关闭文件
return 0;
}
输出:
5.用二进制方式向文件读写一组数据
一般调用形式:
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
其中:
buffer:是一个地址。对fread()来说,它是用来存放从文件读入的数据的存储区地址。对fwrite()来说,是要把此地址开始的存储区中的数据向文件输出(以上指的是起始地址)。
size:要读写的字节数。
count:要读写多少个数据项(每个数据项的长度为size)。
fp:FILE类型的指针。
例如:
#include <stdio.h>
int main() {
// 定义要写入文件的整数数组
int numbers[] = { 1, 2, 3, 4, 5 };
int size = sizeof(numbers)/sizeof(numbers[0]); // 计算数组的大小
// 打开文件以写入数据
FILE* file = fopen("data.bin", "wb");
if (file == NULL) {
perror("无法打开文件以进行写入");
return 1;
}
// 使用 fwrite() 写入数据
size_t items_written = fwrite(numbers, sizeof(int), size, file);
if (items_written != size) {
perror("写入文件时出错");
fclose(file);
return 1;
}
// 关闭文件
fclose(file);
// 重新打开文件以读取数据
file = fopen("data.bin", "rb");
if (file == NULL) {
perror("无法打开文件以进行读取");
return 1;
}
// 定义一个数组来存储从文件中读取的数据
int read_numbers[5];
// 使用 fread() 读取数据
size_t items_read = fread(read_numbers, sizeof(int), size, file);
if (items_read != size) {
perror("从文件中读取数据时出错");
fclose(file);
return 1;
}
// 关闭文件
fclose(file);
// 输出读取的数据,以验证它是否与原始数据匹配
printf("从文件中读取的数据:\n");
for (int i = 0; i < size; i++) {
printf("%d ", read_numbers[i]);
}
printf("\n");
return 0;
}
输出:
6.随机读写数据文件
rewind函数的作用是:使文件file1的文件位置标记重新定位于文件开头,同时feof函数的值会恢复为假。
fseek函数用于改变文件指针指向的位置
调用方式:fseek(文件类型指针,位移量,起始点)
“起始点”用0,1,2代替,0代表“文件开始位置”,1为“当前位置”,2为“文件末尾位置”。
起始点 | 名字 | 用数字代表 |
文件开始位置 | SEEK_SET | 0 |
文件当前位置 | SEEK_CUR | 1 |
文件末尾位置 | SEEK_END | 2 |
"位移量"是指以“起始点”为基点,向前移动的字节数。位移量应是long型数据(在数字末尾加一个字母L,就表示是long型,可省略不写)。
fseek函数一般用于二进制文件。
例如:
fseek(fp,100L,0); 将文件位置标记移到离文件开头100个字节处
fseek(fp,50L,1); 将文件位置标移到离当前位置50个字节处
fseek(fp,-10L,2); 将文件位置标记从文件末尾处向后退10个字节
流式文件、文本文件和二进制文件的关系
1. 流式文件(Stream Files):
- 流式文件是一种按照连续的字节流来处理的文件,数据是逐个字节地从源头流向目的地。
- 流式文件可以是从网络、磁盘或其他设备中读取或写入的数据流。它们通常用于实时处理数据或处理大量数据而不必将其完全加载到内存中。
2. 文本文件(Text Files):
- 文本文件是以文本形式存储的文件,其中包含了人类可读的字符数据,如ASCII或Unicode字符。
- 文本文件可以通过文本编辑器打开和编辑,内容通常是可读的,并且可以被人类理解。
3. 二进制文件(Binary Files):
- 二进制文件是以二进制形式存储的文件,其中包含了计算机可理解的数据,这些数据可能不是以人类可读的形式呈现。
- 二进制文件可以包含任何类型的数据,例如图像、音频、视频、可执行文件等。
- 与文本文件不同,二进制文件的内容不一定是可读的或可理解的。
关系:
- 流式文件是一种处理数据的方式,它可以用来读取或写入文本文件或二进制文件。
- 文本文件和二进制文件都可以通过流式文件进行读取或写入操作。
- 文本文件和二进制文件都可以是流式文件的一种形式,取决于如何处理它们的数据流。
ftell函数用于得到流式文件中文件位置标记的当前位置。如果调用函数出错(如不存在fp指向的文件),ftell函数返回值是-1.
#include <stdio.h>
int main() {
FILE* file = fopen("example.txt", "w+"); // 打开文件进行读写,如果文件不存在则创建
if (file == NULL) {
perror("无法打开文件");
return 1;
}
// 写入一些数据到文件
fprintf(file, "Hello, World!\n");
fprintf(file, "This is a test file.\n");
// 获取当前文件位置
long current_pos = ftell(file);
printf("文件指针当前位置: %ld\n", current_pos);
// 将文件位置指针移动到文件开头
if (fseek(file, 0, SEEK_SET) != 0) {
perror("fseek failed");
fclose(file);
return 1;
}
// 从文件开头读取内容
char buffer[100];
if (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("从文件开始读取到的一行: %s", buffer);
}
// 将文件位置指针移动到文件末尾
if (fseek(file, 0, SEEK_END) != 0) {
perror("fseek failed");
fclose(file);
return 1;
}
// 获取并打印文件末尾位置
current_pos = ftell(file);
printf("当前文件末尾的位置是: %ld\n", current_pos);
// 将文件位置指针移动到之前写入的位置之后
if (fseek(file, current_pos - 10, SEEK_SET) != 0) { // 假设我们知道要读取的位置是文件末尾前10个字节
perror("fseek failed");
fclose(file);
return 1;
}
// 从指定位置读取内容
if (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("从指定位置读取到的内容: %s", buffer);
}
// 关闭文件
fclose(file);
return 0;
}
15.判定文件读取结束的方式
16.指向结构体变量的指针
为了使用方便和直观,C语言允许把(*p).num用p->num代替,"->"代表一个箭头,p->num表示p所指向的结构体变量中的num成员.同样,(*p).name等价于p->name,"->"
称为指向运算符。
如果p指向一个结构体变量stu,则一下三种等价:
1.stu.成员名(如stu.num);
2.(*p).成员名(如(*p).num);
3.p->成员名(如p->num);
17.变量的作用域和存在性情况
变量存储类别 | 函数内 | 函数外 | ||
作用域 | 存在性 | 作用域 | 存在性 | |
自动变量和寄存器变量 | √ | √ | × | × |
静态局部变量 | √ | √ | × | √ |
静态外部变量 | √ | √ | √(仅限本文件) | √ |
外部变量 | √ | √ | √ | √ |
18.类型重命名typedef
方法:按定义变量的方式,把变量名换上新类型名,并且在最前面加typedef,就声明了新类型名代表原来的类型。
例1:
- 先按定义数组变量的形式书写:int a[100];
- 将变量名a换成自己命名的类型名:int Num[100];
- 在前面加上typedef,得到typedef int Num[100];
- 用来定义变量:Num a;相当于定义了:int a[100];
例2:
- char* p; //定义变量p执行一个字符型数据
- char* String; //用新类型名String取代变量名p
- typedef char* String; //加typedef
- String p; //用新类型名String定义变量,相当于char* p;
以上的方法实际上是为特定的类型指定了一个同义字(synonyms)。例如:
1.typedef int Num[100];
Num a; (Num是int [100]的同义词,代表有100个元素的整型数组)
2.typedef int(*Pointer)();
Pointer p1; (Pointer是int(*)()的同义词。代表指向函数的指针类型,函数的返回值类型是int)