目录
一、Q&A
1.#include的两种引用方式
Q:#include有两种引用方式,一种是#include< >,一种是#include" ",这两种有什么差异?
A:#include < >
:这种形式用于引用编译器的标准库头文件。编译器会在标准库路径下查找相应的头文件。路径的具体位置取决于编译器和操作系统,通常包括了标准C/C++库的头文件,如 <stdio.h>
、<stdlib.h>
等。
#include" "查找头文件的方式是在程序目录下去查找,这种方式一般是用来引用自己编写的头文件,如果在程序目录下找不到,他会在编译器类库的路径下再进行查找。
2.bool类型与C语言
Q:bool类型是C语言的内置类型吗?
A:bool类型并不是C语言内置类型,C语言的内置类型包括(char、int、short、long、float……)。
然而,随着时间的推移,C语言的标准发展,特别是在C99标准和之后的标准中,引入了stdbool.h
头文件,并定义了bool
、true
和false
这些宏。这使得bool
类型变得更加常见,尽管它实际上是一个宏,而不是内置类型。
#include <stdbool.h>
bool flag = true;
一些编译器可能会提供对bool
的原生支持,但需要清楚这不是C语言标准的一部分。在vs studio中,如果创建的文件类型为.cpp的话,是支持bool类型的,如果创建的源文件为.c类型,是不支持bool类型的。
Q:随机定义的bool类型为真还是为假?
A:随机定义一个bool的局部变量为真,随机定义一个bool全局变量为假。static定义的未赋初值的bool类型变量,程序默认初始值为0。
3.函数的类型
Q:函数有没有类型?如果有是什么类型?
A:函数有类型,函数的类型包括两部分:形参的类型和返回值的类型。
如果函数类型一致的话,可以使用同一个函数调用。
例子如下:下面的例子可以使用int(*p)(int,int)来获取add函数和sub函数的地址,就是因为他们的函数类型一致。
int add(int a ,int b)
{
return a + b;
}
int sub(int a,int b)
{
return a - b;
}
int main()
{
int(*p)(int,int);
p = &add;
p = ⊂
}
4.指针的相关声明
Q:请解释int *par[10]、int(*p)[10]、int(*p)(int,int)这几个声明的含义?
A:声明的解释如下
int *par[10]:从右向左解释,有10个元素的数组,每个元素都是指针,每个指针都是整型。总结来说就是包含10个整型指针元素的数组。
int *par[10]; // 声明了一个包含10个整数指针的数组
int(*p)[10] :有括号,先解释括号,这个指针包含10个元素的数组,这个数组是整型类型的。总结来说,这个指针是存放包含10个整型元素的数组。
int arr[10]; // 声明了一个包含10个整数的数组
int(*p)[10] = &arr; // p指向arr这个包含10个整数的数组
int(*p)(int,int):有两个括号,先解释有标识符的括号。这个声明是声明了一个指针指向包含有两个整型形参且返回值是整型的函数。这种声明通常用于指向函数的指针,以便在运行时动态调用不同的函数。
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
int (*p)(int, int); // 声明一个指向函数的指针
p = add; // 指向add函数
int result = p(5, 3); // 调用add函数,result将等于8
p = sub; // 指向sub函数
result = p(5, 3); // 调用sub函数,result将等于2
int* ip :其中int*意思是整型类型的指针,声明 int* ip;
后,ip
可以被用来指向一个整数变量的地址。
int number = 42; // 定义一个整数变量 number 并赋值为 42
int* ip; // 声明一个整数指针 ip
ip = &number; // 将 ip 指向整数变量 number 的地址
5.函数调用和取地址
Q:请举一个例子来展示函数的调用和取地址怎么写?
A:函数调用后面加括号,括号里写需要的实参;函数取地址后面不用写括号,直接赋给指针就好。
int add(int a, int b) {
return a + b;
}
int main()
{
int result = add(5, 3); // 调用 add 函数,result 将等于 8
int (*ptrToAdd)(int, int); // 声明一个指向接受两个 int 参数并返回 int 的函数指针
ptrToAdd = add; // 将函数 add 的地址赋给指针
}
6.exit()函数
exit
函数是C语言标准库中的一个函数,它用于正常终止程序的执行。exit
函数接受一个整数参数,该参数通常被称为退出状态码(exit code),用于表示程序的退出状态。在程序正常终止时,可以使用 exit
函数来返回一个退出状态码。
exit
函数的原型如下:
void exit(int status);
state:要返回的退出状态码,通常是整数值。根据约定,0 表示程序成功执行,而非零值通常表示程序出现了错误或异常情况。以下是 exit
函数的一些使用示例:
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("程序开始执行...\n");
// 正常终止程序并返回退出状态码 0(表示成功执行)
exit(0);
// 下面的代码不会被执行
printf("这行代码不会被执行...\n");
return 0;
}
7. 可变参…
Q:函数形参中的三个点...代表什么?
A:它是可变参的意思,通常指的是函数或方法可以接受不定数量的参数。在许多编程语言中,可变参数允许你调用函数时传递不同数量的参数,而不需要提前声明函数接受的参数数量,这些参数的类型也可以灵活选取。
示例代码如下:
int func(int a,...)
{
//函数体
}
int main()
{
func(1);//可以是一个参数,
func(1,"lizeyu");//可以是两个
func(1,2,"lizey",2.0)//可以是四个,类型也可以随便取
}
二、结构体
1.概念
结构体由多个成员变量组成,每个成员变量可以是不同的数据类型(如整数、浮点数、字符、其他结构体等)。这允许你在一个结构体中存储和管理相关联的数据。结构体是一种类型,是由程序开发者自己设计的类型。
类型是设计的产物,不能说定义类型。
2.语法
struct 结构体名
{
数据类型 成员1
数据类型 成员2
……
}
3.示例
在定义变量时,必须要加上关键字struct才能进行定义,否则不能进行定义。
struct Student
{
char s_id[20];
char s_name[20];
char s_sex[10];
int s_age;
};
int main()
{
struct Student s;//必须加上struct才能定义哦,否则不能定义
s.s_id = "22222222";
s.s_name = "LZY";
s.s_sex = "男";
s.s_age = 23;
}
在构建结构体时,花括号内部的都是属性,不能被直接初始化,只有定义了结构体变量(实体)之后才能进行初始化。
在程序编译的过程中,只有定义了结构体实体之后,计算机才会给对应的实体分配空间。
4.初始化结构体
结构体初始化的顺序要和结构体属性声明顺序一致。结构体实体花括号内依次从左到右对应属性从上到下。
struct Student
{
char s_id[20];
char s_name[20];
char s_sex[10];
int s_age;
};
int main()
{
struct Student s = {"2323256456","lizeyu","男",23};
}
如果没有初始化,计算机就会随机给属性赋值,一般赋字符c,当鼠标悬停在定义的实体上,就会发现计算机给属性赋值后显示为“烫”,那是因为“0xcc”在16进制中表示汉字“烫”。
5.输入结构体
使用“.”,成员选择符
使用方式:对象.成员名。其中,结构体对象也被称为实体。
struct Student
{
char s_id[20];
char s_name[20];
char s_sex[10];
int s_age;
};
int main()
{
struct Student s1;
scanf("%s %s %s %d", s1.s_id, s1.s_name, s1.s_sex, &s1.s_age);
printf("%s %s %s %d\n", s1.s_id, s1.s_name, s1.s_sex, s1.s_age);
}
Q: 为什么结构体输入的时候只有s1.s_age前面需要加取地址&符号,其他不用添加&符号?
A:因为需要知道整数存储到哪一个内存地址了,但是s1.s_id,s1.s_name等他们本身就是字符数组,数组名称的本身就代表了数组的地址,所以不需要取地址符号&。在C语言中,字符数组名本身就是指向字符数组的指针,表示数组的首地址。
6.输出结构体
struct Student
{
char s_id[20];
char s_name[20];
char s_sex[10];
int s_age;
};
int main()
{
struct Student s = {"2323256456","lizeyu","男",23};
printf("%s %s %s %d",s.s_id,s.s_name,s.s_sex,s.s_age);
}
另外,结构体可以整体赋值
s2 = s1;
struct Student
{
char s_id[20];
char s_name[20];
char s_sex[10];
int s_age;
};
int main()
{
struct Student s1 = { "2323256456","lizeyu","男",23 };
printf("%s %s %s %d\n", s1.s_id, s1.s_name, s1.s_sex, s1.s_age);
struct Student s2;
s2 = s1;
printf("%s %s %s %d\n", s2.s_id, s2.s_name, s2.s_sex, s2.s_age);
}
7.结构体指针
struct Student
{
char s_id[20];
char s_name[20];
char s_sex[10];
int s_age;
};
int main()
{
struct Student s1;
struct Student* p = NULL;
p = &s1;
scanf("%s %s %s %d", s1.s_id, s1.s_name, s1.s_sex, &s1.s_age);
printf("p size:%d\n", sizeof(p));
}
在代码中,计算了指向结构体 Student
的指针 p
的大小,结果为4个字节。这个结果是因为在典型的32位系统中,指针的大小通常是4个字节。这意味着指针 p
存储了结构体 Student
的首地址。
p
存储的是结构体 s1
中第一个成员 s_id
的地址。在内存中,结构体的成员按照声明的顺序依次排列,所以 s_id
是第一个成员,其地址也是结构体的地址。
需要注意的是,指针的大小在不同的系统和编译器中可能会有所不同。在典型的32位系统中,指针通常是4个字节,而在64位系统中,指针通常是8个字节。因此,sizeof(p)
的结果可能会根据系统的位数和编译器而异。
8.指针调用属性
根据优先级,这样写
(*p).s_id
如果*p是s1,那么就相当于s1.s_id。
不能直接这样写,如下:
*p.s_id
这样写错误的原因是符号选择运算符“.”优先级高于*,所以是直接对p调用属性,p的值是地址,地址是不能被调用属性的,所以程序会报错。
如果不想要带*,可以使用下面这行代码,使用“->”指向符。
p->s_id
9.结构体对齐
首先我们看下面一个例子:
struct Node
{
char cha;
int b;
char chc;
};
int main()
{
printf("%d", sizeof(struct Node));
}
我们计算该结构体的字节大小,此时输出的结果为12。
我们将结构体的int类型和char类型换一下位置
struct Node
{
char cha;
char chc;
int b;
};
int main()
{
printf("%d", sizeof(struct Node));
}
再次输出该结构体的大小,我们可以看到此时的结构体的大小为8。
上面的问题是由于结构体内存对齐规则差异导致的,下面我们详细说一下结构体的内存对齐规则:
产生内存对齐的原因
1. 提高访问效率处理器访问内存时,是以字/双字为单位进行的。对齐可以提高访问效率。
2. 遵循处理器访问规则许多处理器要求数据在内存中的地址必须是其字节数的整数倍。
总结:牺牲内存换效率
内存对齐的原则
1.速成版
2.大神详解
三、文件
1.文件分类
2.文件读写流程
读文件:先将文件的数据复制到缓冲区,再读取缓冲区的数据到程序中。
写文件:程序先将文件写到缓冲区中,通过刷新文件或者关闭文件将缓冲区的数据写入到磁盘中。
3.打开文件
C语言对文件的操作包含三部分:打开文件、读写文件、关闭文件
FILE* fopen =(filename,mode);
filename:文件名,包含文件路径。
mode:对文件可进行的操作。
常用的操作如下:
4.字符、字符串的输入输出
5.输出函数
printf
printf是标准输出函数,用于将格式化的数据输出到控制台(终端)上。它接受格式字符串作为第一个参数,这些字符串先被存储在缓冲区中,然后根据格式字符串的指示将数据输出到标准输出流(通常是屏幕)。因此,你在屏幕上看到的10,并不是数字,而是在缓冲区被转换成字符的1和0。
sprintf
sprintf是字符串输出函数,它将格式化的数据写入一个字符数组中,而不是输出到控制台。它的使用方式与 printf
类似,但它将结果存储在一个字符数组中,这个字符数组就是缓冲区buffer。
char buffer[100];
int num = 42;
char str[] = "Hello, World!";
sprintf(buffer, "Number: %d\nString: %s\n", num, str);
buffer的返回值是不包括终止符“/0”的,生成字符串的字符数。
fprintf
fprintf是文件输出函数,用于将格式化的数据写入文件而不是输出到控制台。它与 printf类似,但需要指定要写入的文件流作为第一个参数。
int main()
{
FILE* fp = fopen("E:\\图论\\lizeyu.txt", "w");
if (fp != NULL)
{
int num = 42;
char str[] = "Hello world!";
fprintf(fp, "Number:%d\n String:%s\n", num, str);
fclose(fp);
fp = NULL;
}
else
{
printf("无法打开文件\n");
}
}
6.关闭文件
写文件,其实先将输入转化成字符串,输入到buffer缓冲区,当被刷新,或者关闭,或者数据太多缓冲区写不下才会被存入磁盘。
刷新文件
刷新文件会使得存储在缓冲区的数据被刷新到磁盘中。
fflush(fp);//刷新名称位fp的文件
关闭文件
如果打开文件后没有刷新,或者不关闭文件,文件可能在缓冲区没有被存入磁盘,导致文件没有真正写入。
fclose(fp);
fp = NULL;
fp
是一个指向文件的指针,用于打开和操作文件。在文件操作完成后,特别是在关闭文件之后,将指针fp
设置为NULL
是一种良好的编程习惯。设置
fp = NULL
的主要目的是避免在之后的代码中意外地继续使用已经关闭的文件指针。如果你尝试在已经关闭的文件上执行文件操作,可能会导致未定义的行为或错误
fclose:主要执行两个操作:
1.将缓冲区数据存储到磁盘中。
2.将文件结构释放。
文件结构:每个打开的文件都有一个与之关联的文件结构(file structure),该结构包含有关文件的信息,如文件位置指针、文件打开模式等。当你调用 fclose
函数时,它会释放与文件相关的文件结构,从而释放系统资源,以确保文件不再被进程使用。这意味着在调用 fclose
之后,你不能再对文件执行读写操作,除非你再次打开它。
读文件
int main()
{
FILE* fp = fopen("E:\\图论\\lizeyu.txt", "r");
char buffer[100];//存储读取的字符
if (fp == NULL)
{
printf("无法打开文件\n");
return 1;
}
else
{
if (fgets(buffer, sizeof(buffer), fp)!=NULL)
{
printf("第一行内容:%s\n", buffer);
}
}
fclose(fp);
fp = NULL;
return 0;
}
7.标准文件
eg:
fprintf(stdout,"Numer:%d\nString:%s\n",num,str);
此时写入的内容将会被显示在屏幕上。
8.文件协议
文件协议(File Protocol)通常指的是一种规定或约定,用于在计算机系统中访问和传输文件的方式和协议。
因此在进行编写文件的时候,不仅要写文件内容,还要写文件更多的信息,例如:文件的数据量。方便后续根据这些信息更好的进行读取操作。
常见的文件协议:
四、关键字
C语言的关键字共有32个,根据关键字的作用,可分为数据类型关键字,控制语句关键字。
1.sizeof
sizeof在编译时候进行计算字节个数,而不是在执行时。
示例一:
int main()
{
int a = 10;
int size = sizeof(++a);
printf("a =%d\nsize = %d\n", a, size);
return 0;
}
程序并不在乎,++a执行的操作,在进行++a操作之前,sizeof(++a)就已经被替换成4了,4是a的类类型所占的字节大小,整型的长度。
示例二:
#include<string.h>
int main()
{
char str[] = { "lzy\0ing" };
int len = strlen(str);
int size = sizeof(str);
printf("len = %d\nsize=%d\n", len, size);
return 0;
}
strtlen()计算的是字符串的长度,不计算编译过程中的\0,\0是字符串的结束符(终止符),所以这个字符串到\0就结束了,总的字符串的长度为3。
sizeof()用来计算在编译过程中,变量或者类型的字节个数,在编译时,字符串末尾程序会自动加上\0,所以size的字节个数为8个。
sizeof()求字节大小的书写方式
计算变量字节大小时可以加括号,也可以不加括号;计算类型大小时,必须要给类型加括号,否则报错。
int main()
{
int a = 10;
sizeof(a);//正确 求变量a的字节大小
sizeof a;//正确 求变量a的字节大小
sizeof int;//错误 求int类型的字节大小,需要给类型加上括号
sizeof(int);//正确 求int类型的字节大小
}
2.volatile
volatile是C语言中的一个关键字,用于声明变量具有易失性(volatile)属性。变量声明为 volatile
告诉编译器不要对该变量进行某些优化,因为该变量的值可能会在程序的控制之外被修改,例如被硬件或其他线程。它主要用于以下情况:
硬件寄存器:在嵌入式系统和底层编程中,通常会使用 volatile
来声明与硬件寄存器交互的变量。硬件寄存器的值可以随时由硬件更改,因此编译器不应该对它们进行优化,以确保读取和写入寄存器的操作不被编译器删除或重排序。
多线程编程:在多线程环境中,如果一个变量被多个线程同时访问并且其中一个线程修改了它的值,那么其他线程也应该能够看到这个变化。在这种情况下,将变量声明为 volatile
可以防止编译器对变量的访问进行优化,确保每次都从内存中读取变量的值。
虽然 volatile
可以确保变量的值不会被编译器优化掉,但它并不提供线程同步功能。在多线程编程中,通常需要使用互斥锁或其他同步机制来确保线程安全性。(待学习)
volatile
关键字用于告诉编译器变量的值可能会在程序的控制之外被修改,因此编译器不应该对这些变量的访问进行优化。这对于与硬件交互或在多线程环境中使用共享变量非常有用。
参考文献
1.C语言学习_include<>与include""的区别 - Wenism - 博客园 (cnblogs.com)
2. 杨和平.课件.图论教育