目录
C语言学习记录01
一、C语言基本框架
1. main函数
● 所有C程序都从`main`函数开始执行。在文档中,main函数的定义如下:
int main()
{
return 0;
}
● 这里int是函数的返回值类型,表示main函数将返回一个整数。
二、返回值以及返回值类型
1. 程序正常退出
● 文档中提到return 0;,这是main函数的返回语句。在C语言中,return语句用于结束函数的执行,并将一个值返回给调用者。
● return 0;通常用于表示程序正常退出。这里的0是返回值,按照惯例,0表示成功,非零值表示错误。
三、头文件
1. 头文件的作用
● 头文件(.h文件)用于存放函数的声明。当在程序中调用一个函数时,必须在调用之前声明该函数。函数声明告诉编译器函数的名称、参数类型和返回值类型。
● 例如,当使用`printf()`函数时,需要包含<stdio.h>头文件,因为printf()函数的声明在<stdio.h>中。
● 文档中提到了一个例子:使用printf()函数时,需要包含<stdio.h>头文件,因为printf()函数的声明在<stdio.h>中。如果没有包含该头文件,编译器将不知道printf()函数的存在,会报错。
2. 头文件的位置(Linux环境下)
● 在Linux系统中,头文件通常存放在/usr/include目录下。
● 例如,<stdio.h>头文件就在/usr/include目录下。
3. 在Linux下打开头文件
● 文档中介绍了如何在Linux下使用`gedit`工具打开头文件。
● 命令如下:
gedit /usr/include/stdio.h
● 这行命令使用gedit文本编辑器打开/usr/include目录下的stdio.h头文件。
4. printf()函数声明
● 文档中还展示了printf()函数的声明:
extern int printf(const char * restrict format,...);
● extern关键字表示该函数可能在其他文件中定义。
● int是返回值类型,表示printf()函数返回一个整数。
● printf是函数名。
● const char * restrict format是第一个参数,表示格式化字符串。
● ... 表示可变参数,printf()函数可以接受多个参数。
2、问题
如何知道一个函数的头文件是哪个?如何使用man手册去查询头文件是什么?
3、方法
文中给出了两个示例,分别是查找printf()函数和socket()函数的头文件。
示例一:查找printf()函数的头文件
1. 第一步:打开Ubuntu
● 这意味着你需要进入Linux系统环境。
2. 第二步:在终端命令行输入:man 3 printf
● 这里man是手册(manual)的缩写,是Linux系统中用于查看命令或函数帮助文档的工具。
○ 3表示手册的章节,在Linux手册中,不同的章节有不同的内容:
✧ 1:用户命令
✧ 2:系统调用
✧ 3:C库函数
✧ 4:设备文件
✧ 5:文件格式
✧ 6:游戏
✧ 7:杂项
✧ 8:系统管理命令
printf是你要查找的函数名。
3. 第三步:得知头文件:#include <stdio.h>
● 在man手册中查找printf函数时,会看到该函数的相关信息,包括它的头文件是<stdio.h>。
4. 第四步:按q可以返回终端
● 这是man手册的操作方法,按`q`键可以退出查看手册的界面。
示例二:查找socket()函数的头文件
1. 第一步:打开Ubuntu
● 同样,需要进入Linux系统环境。
2. 第二步:在终端命令行输入:man 3 socket
● 这里man和3的含义与上一个示例相同。
● socket是要查找的函数名。
3. 第三步:得知头文件:#include <sys/socket.h>
● 在man手册中查找socket函数时,会看到该函数的相关信息,包括它的头文件是<sys/socket.h>。
4. 第四步:按q可以返回终端
● 同样,按q键可以退出查看手册的界面。
总结
通过使用Linux系统中的`man`手册,可以方便地查找C库函数的头文件。只需要在终端中输入man加上手册章节号和函数名,就可以找到对应的头文件信息。
四、命令行传递参数
1. 命令行传参的概念
● 定义
○ 程序执行命令是:./xxx,其实也可以在执行的时候,传递一些参数给程序中使用,其命令是:./xxx arg1 arg2...
○ 这意味着在执行程序时,可以在程序名后面跟上一些参数,这些参数可以在程序内部被使用。
2. 例子
● 示例一
○ 命令:gec@ubuntu:/mnt/hgfs/GZ2153/01 C预科/01/code$ ./p
○ 解释:这只是执行程序,但没有额外给命令行传递参数。
● 示例二
○ 命令:gec@ubuntu:/mnt/hgfs/GZ2153/01 C预科/01/code$./p aaa bbb
○ 解释:除了执行程序之外,还给程序额外传递了参数。
3. 分析
○ 问题:为什么程序结果都是一样的?
○ 答案:因为我们没有在程序中接收额外的参数以及处理这些参数。
4. 如何在程序中接收额外的参数?
● 不接收参数的写法
○ int main()
○ int main(void)
○ 解释:这两种写法都表示不接收任何参数,也就是说,即使命令行中传递了参数,也不会接收。
● 接收参数的写法
○ int main(int argc, char *argv[])
○ 解释:这种写法可以正确接收参数。
● 参数含义
○ argc:argument count,代表接收有多少个参数传递过来。
○ argv:argument value,代表接收每一个参数分别是什么。
○ argv[0]:接收第一个参数。
○ argv[1]:接收第二个参数,以此类推。
5、代码示例
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("argc = %d\n", argc);
printf("argv[0] = %s\n", argv[0]);
printf("argv[1] = %s\n", argv[1]);
return 0;
}
● 这段代码包含了<stdio.h>头文件,这是C语言标准输入输出库的头文件,提供了printf函数的声明。
● main函数接收两个参数:argc和argv。
● argc(argument count)是一个整数,表示传递给程序的参数个数。
● argv(argument value)是一个字符指针数组,每个元素指向一个传递给程序的参数字符串。
6、 运行命令和结果
● 运行命令:./test1 aaa
○ 这里./test1是可执行程序的名称,aaa是传递给程序的参数。
● 运行结果
○ argc = 2
○ argv[0] =./test1
○ argv[1] = aaa
○ 解释:
argc的值为2,因为有两个参数传递给程序。第一个参数是程序名./test1,第二个参数是aaa。
argv[0]指向程序名./test1。
argv[1]指向传递的参数aaa。
总结
● 这段代码演示了如何在C语言中使用命令行参数。
● 通过argc和argv,程序可以获取到传递给它的参数信息。
● 在运行程序时,命令行中的每个单词都会被当作一个参数,程序名本身也算作一个参数。
● 对于一些易变的值,使用命令行传递参数可避免频繁的修改代码。
五、编译问题
1、编译时编译器做了什么?
编译器在编译时主要检查代码的语法是否正确。它会对代码进行语法分析,确保代码符合编程语言的语法规则。
2、编译之后的三种结果
编译通过
● 特点:编译完成后,会直接出现一个新的二进制程序。
● 结果:生成一个新的二进制程序。
编译警告
● 示例:不添加头文件。
● 特点:编译完之后,会非常明显地看到一个警告(warning)。虽然可以运行,但可能会出现异常。
● 结果:生成一个新的二进制程序。
编译出错
● 示例:缺少一个分号。
● 特点:编译完之后,会非常明显地看到一个错误(error)。
● 结果:没有生成新的二进制程序,必须要解决这个问题才可以。
3、编译通过不代表功能实现
即使代码编译通过,也不一定意味着功能就一定能实现。因为编译通过只是说明代码没有语法错误,但功能不一定按照预期实现。这可能是因为逻辑错误或者对需求的误解等原因导致的。
总结、编译过程主要是检查语法错误,编译结果有通过、警告和出错三种情况,而编译通过并不保证程序的功能完全正确。
4、编译模式
● 适用语言:C、C++、Go等。
● 工作原理:
○ 将代码提供给编译器,编译器根据运行平台直接进行编译,生成可执行的二进制文件。
● 优点:
○ 性能快,效率高。因为编译后的二进制文件可以直接在目标平台上运行,不需要额外的翻译过程。
● 缺点:
○ 换一个平台需要再编译一次。这是因为不同平台的二进制文件格式和指令集可能不同,需要重新编译才能在新平台上运行。
5、解释模式
● 适用语言:Java、C#、Lua等。
● 工作原理:
● 需要额外增加一个解释器。解释器将代码根据平台的不同,翻译成相应平台的可执行二进制文件。
● 优点:
● 跨平台。由于有解释器在运行时进行翻译,代码可以在不同平台上运行,只要有相应平台的解释器即可。
● 缺点:
● 牺牲性能。因为在运行时需要进行翻译,这会增加运行时的开销,导致性能下降。
总结来说,编译模式适合对性能要求高的场景,但缺乏跨平台性;而解释模式则适合需要跨平台运行的场景,但性能上会有所损失。
六、数据类型
1、什么是数据类型?
数据类型描述的是一个变量在内存中占用多少个字节的空间。
2、数据类型的种类
基本数据类型
● 在Linux系统下本来就存的数据类型,我们直接使用即可。
● 包括:char、short、int、`long`、`float`、`double (true/flase)`。
非基本数据类型
● 在Linux系统是不存在,必须在程序中先声明这些非基本数据类型,才可以使用。
● 包括:数组、指针、结构体等。
3、基本数据类型可以存放什么数据?
char(字符型)
● 这种类型的变量是用于存放字符型的数据。
● 范围:-2^7到2^7-1,即 -128到127。
short(短整型)
● 这种类型的变量是用于存放短整型的数据。
● 范围:-2^15到2^15-1。
int(整型)
● 这种类型的变量是用于存放整型的数据。
● 范围:-2^31到2^31-1(常用)。
long(长整型)
● 这种类型的变量是用于存放长整型的数据。
● 范围:-2^63到2^63-1。
float(单精度浮点型)
● 这种类型的变量是用于存放单精度浮点型的数据。
● 默认保存小数点的后6位。
6. double(双精度浮点型)
● 这种类型的变量是用于存放双精度浮点型的数据。
● 默认保存小数点的后15位。
3.1、注意
C语言中有字符串,但是没有字符串类型。
总结来说,数据类型定义了变量在内存中的存储方式和占用空间,C语言中的数据类型分为基本数据类型和非基本数据类型,每种数据类型都有其特定的用途和存储范围。
4、sizeof关键字
● sizeof关键字用于计算内存空间占用的字节数。
● sizeof返回值是一个长整型数据,输出结果需要使用%ld格式化输出。
4.1、示例代码
#include <stdio.h>
// 求基本数据类型占用的空间分别是多大
int main(int argc, char *argv[])
{
printf("%ld\n", sizeof(char)); // 1
printf("%ld\n", sizeof(short)); // 2
printf("%ld\n", sizeof(int)); // 4
printf("%ld\n", sizeof(long)); // 8
printf("%ld\n", sizeof(float)); // 4
printf("%ld\n", sizeof(double));// 8
return 0;
}
● 这段代码使用sizeof关键字计算了不同基本数据类型在内存中占用的字节数,并通过printf函数输出结果。
5、基本数据类型占用字节数
1. char:占用1字节。
2. short:占用2字节。
3. int:占用4字节。
4. long:占用8字节。
5. float:占用4字节。
6. double:占用8字节。
结论
1. 如果在程序中声明一个int类型的变量,那么这个变量就占用4个字节。
2. 基本数据类型占用的空间大小由编译系统来决定。通过`sizeof`关键字可以方便地计算出不同基本数据类型在内存中占用的字节数,所以这些字节数在不同的编译系统中可能会有所不同。
七、如何定义变量?
1、定义公式
数据类型
● 从基本数据类型中选择一个。
● 规则A:只能使用数字、字母、下划线组成。
● 反例:`int a+5`(错误,因为有`+`符号)
● 正确:`int a5`
● 规则B:不能以数字开头。
● 反例:`5a`(错误,因为以数字开头)
● 正确:`a5`
● 规则C:不能与系统关键词重名。
● 反例:`int return`(错误,因为`return`是系统关键词)
● 正确:`int Return`
2、定义变量的意义
定义变量的意义
● 定义变量意味着在内存中为变量分配空间,并决定了变量的数据类型和名称。
3、两个易错问题
问题1
● 定义一个整型变量,名字为`x`。
● 正确写法:`int x;`
● 错误答案:可能会写成`int x`(缺少分号)
问题2
● `int x;`这行代码的含义是什么?
● 错误答案:可能会错误地认为只是定义了一个变量。
● 正确答案:在内存中连续申请4个字节的空间,然后使用变量`x`间接访问这片空间。
总结:
● 定义变量时要遵循数据类型选择和命名规则。
● 定义变量的本质是在内存中为变量分配空间。
● 常见的易错点包括忘记分号和错误理解变量定义的含义。
八、内存分配原则
● 内存地址的连续性:在分配空间时,内存地址是连续的。这意味着变量在内存中的存储位置是依次排列的。
● 内存空间的空闲性:分配空间时,内存上的空间必须是空闲的。即之前被其他变量占用的内存空间不会再被分配给新的变量。
● 空间位置的不确定性:申请下来的空间位置是不确定的。尽管内存地址是连续的,但变量在内存中的具体起始位置由操作系统或编译器决定,程序员无法预先知道。
1、示例
#include <stdio.h>
int main()
{
int x; //在内存空间连续申请4个字节,然后使用x来访问这4个字节的空间。
printf("%p\n", &x);
//& -> 取地址符
//&x -> 求出x这个变量的地址
return 0;
}
2、解释
● 头文件包含:`#include <stdio.h>`是C语言中的预处理指令,用于包含标准输入输出头文件。这个头文件提供了像`printf`这样用于输出的函数。
● 主函数:`int main()`是C程序的入口点。程序从`main`函数开始执行。
● 变量定义:`int x;`定义了一个名为`x`的整型变量。在内存中,这会为`x`分配4个字节的连续空间(因为`int`类型通常占用4个字节)。
● 地址输出:`printf("%p\n", &x);`这行代码用于输出变量`x`的内存地址。`%p`是`printf`函数的格式控制符,用于以十六进制形式输出指针(地址)。`&x`表示取变量`x`的地址。
● 函数返回:`return 0;`表示`main`函数正常结束,并返回0给操作系统。按照惯例,0通常表示程序正常退出。
3、总结
● 连续:内存分配时地址是连续的。
● 空闲:分配的空间必须是空闲的。
● 不确定:具体的空间位置是不确定的。
九、变量的赋值
赋值概念
● 定义了一个变量之后,这个变量是可以用来存储数据的。要把想存储的数据给这个变量,就要使用变量的赋值。使用“=”对变量进行赋值,“=”的作用就是把等号右边的值赋给左边的变量。
赋值方式
● 定义变量的同时初始化
● 示例:`int x = 100;`
● 解释:在这个例子中,100这个值在内存中就会存储在`x`所代表的那片空间中。这种方式在定义变量时就赋予了变量初始值。
● 先定义,后初始化
● 示例:
int x;
x = 100;
● 解释:当只定义`int x;`时,变量`x`会出现随机值。然后通过`x = 100;`将100赋值给`x`。
1、示例
#include <stdio.h>
int main(int argc, char *argv[])
{
//第一种情况:
//int x = 100;
//printf("x = %d\n", x);
//第二种情况:
int x;
printf("x = %d\n", x); //随机数
x = 100;
printf("x = %d\n", x);
return 0;
}
2、解释
● 头文件包含:`#include <stdio.h>`包含了标准输入输出头文件,用于支持`printf`函数。
● 主函数:`int main(int argc, char *argv[])`是程序的入口点。
● 第一种情况(注释部分)
● `int x = 100;`:定义并初始化变量`x`为100。
● `printf("x = %d\n", x);`:输出变量`x`的值,此时`x`的值为100。
● 第二种情况
● `int x;`:先定义变量`x`,此时`x`的值是随机的。
● `printf("x = %d\n", x);`:输出变量`x`的值,由于`x`未初始化,会输出一个随机值。
● `x = 100;`:将100赋值给变量`x`。
● `printf("x = %d\n", x);`:再次输出变量`x`的值,此时`x`的值为100。
● 函数返回:`return 0;`表示`main`函数正常结束,并返回0。
3、总结
这段内容详细介绍了变量赋值的两种常见方式,并通过代码示例展示了两种方式的区别,特别是未初始化变量会出现随机值这一特性。这对于理解变量在内存中的存储和操作具有重要意义。
十、变量的生命周期与作用域
1、变量生命周期
(一)全局变量生命周期
1. 描述
● 程序开始执行时,全局变量的空间就会申请,程序结束时,全局变量的空间才会释放。
2. 示例代码
int x; // 全局变量开始申请空间
int main()
{
return 0; //main函数的return代表程序结束 -> 全局变量开始释放空间
}
3. 解释
● 程序启动后,全局变量`x`立即在内存中获取空间,直到`main`函数执行到`return 0;`,程序结束时,`x`所占用的内存空间才被释放,其生命周期贯穿程序始终。
(二)局部变量生命周期
1. 描述
● 局部变量是在函数体内定义时申请空间,当该函数执行完毕返回时,这个局部变量所占用的空间就会被释放。
2. 示例代码
int x; // 全局变量开始申请空间
int func()
{
int y; // 局部变量开始申请空间
return 0; // 函数返回了,在func函数内部申请的局部变量的空间就会释放
}
int main()
{
func();
return 0; //main函数的return代表程序结束 -> 全局变量开始释放空间
}
3. 解释
● 全局变量`x`在程序初始申请内存空间,到`main`函数结束释放。局部变量`y`在`func`函数被调用进入函数体时申请内存,`func`函数返回后,`y`的内存空间随即释放,其生命周期局限于所在函数执行期间。
2、变量作用域
(一)局部变量作用域
1. 定义与特点
● 在程序中的函数体内部定义的变量就是局部变量,如`main`函数中定义的变量。其作用范围限定在定义它的函数内部,函数外部无法访问,保证了函数内部变量的独立性。
(二)全局变量作用域
1. 定义与特点
● 在程序中的函数体外部定义的变量就是全局变量。它在整个程序生命周期内存在,从程序开始执行到结束,其所占内存空间一直保留,可在任何函数内部访问(无同名局部变量屏蔽时),但使用不当会影响程序可读性和可维护性。
3、内存区域分布与变量存储
1. 内存区域分布
● 栈区(.stack):局部变量的所在地。
● 数据段(.bss):未初始化的全局变量的所在地。
● 数据段(.data):已初始化的全局变量的所在地。
● 常量区(.rodata):常量所在地。
2. 示例程序与变量分布解释
int a; // 未初始化的全局变量,存放在数据段(.bss)
int b = 10; // 已初始化的全局变量,存放在数据段(.data)
int main()
{
int c; // 未初始化的局部变量,存放在栈区(.stack)
int d = 20; // 已初始化的局部变量,存放在栈区(.stack)
return 0;
}
暂时先掌握内存空间分布的4个区域
4、变量初始化值
1. 全局变量:默认都是“0”。在C语言中,全局变量若未显式初始化,编译器自动将其初始化为0,因其存储在数据段,程序加载时会被初始化。
2. 局部变量:默认都是随机值。局部变量存储在栈上,函数调用时分配空间且不自动初始化,使用前未初始化则为栈上残留的随机数据。
5、关于局部变量与全局变量的问题
1. 形式参数
● 答案:属于局部变量。
● 解析:形式参数在函数定义时声明,作用域仅限函数内部,函数调用时接收实参值,函数执行完毕后销毁。
2. 不同函数中定义相同变量名
● 答案:可以。
● 解析:不同函数中的变量相互独立,作用域限于各自函数内部,内存存储位置和生命周期不同,不会相互干扰。
3. 全局变量与局部变量同名
● 答案:可以同名,在局部变量作用域内输出局部变量的值。
● 解析
● 全局变量和局部变量可同名,在局部变量作用域内,局部变量屏蔽全局变量。例如:
int x = 10;
int func()
{
int x = 20;
printf("x = %d\n", x);
}
int main()
{
func();
}
● 在`func`函数中,`printf`输出的是局部变量`x`的值20,因为局部变量覆盖了同名全局变量。
理解这些概念对于掌握程序运行时的内存管理和变量访问机制至关重要,有助于编写正确、高效的程序,合理管理内存,避免变量冲突和内存泄漏等问题,提升程序质量和稳定性。
C语言学习记录02
一、 运算符
1、运算符的分类
1. 四则运算
● 包括加(`+`)、减(`-`)、乘(`*`)、除(`/`)。
2. 模运算
● 取余(`%`)。
3. 赋值运算
● 基本赋值(`=`)、加等于(`+=`)、减等于(`-=`)、乘等于(`*=`)、除等于(`/=`)、不等于(`!=`)、等于(`==`)。
4. 逻辑运算
● 与(`&&`)、或(`||`)、非(`!`)。
5. 位运算
● 位与(`&`)、位或(`|`)、位异或(`^`)、左移(`<<`)、右移(`>>`)。
6. 自增自减
● 自增(`++`)、自减(`--`)。
2、逻辑运算中的条件判断
1. 与(`&&`)
● 逻辑与需要两个条件都满足才为真,即“我与你”。
2. 或(`||`)
● 逻辑或只要有一个条件满足就为真,即“我或你”。
3、注意事项
1. 浮点型与整型数据赋值问题
● 当将浮点型数据赋值给整型变量时,精度会降低,小数点后面的数字会丢失。
● 示例:
● `int a = 3.14;`,此时`a`的值是3。
● 当将整型数据赋值给浮点型变量时,会补6个0。
● 示例:
● `float b = 2;`,此时`b`的值是2.000000。
2. 除法易错点
● 在C语言中:
● `5/2`的结果是2(因为是整型除法,结果取整)。
● `5.0/2`、`5/2.0`、`5.0/2.0`的结果都是2.5(因为有浮点数参与运算,结果为浮点数)。
3. 模运算中的注意点
● 模运算中,被除数和除数都不可以是由小数点的。
● 示例:
● `10 % 3`是正确的,结果为1。
● `10.0 % 3`、`10 % 3.0`、`10.0 % 3.0`都是错误的。
二、“=”与“==”的区别
1. “=”的含义
● “=”是赋值运算符,它的作用是把右边的值赋给左边的变量。例如:
● `a = 5;`表示将5赋值给变量`a`。
2. “==”的含义
● “==”是比较运算符,用于判断左边的值是否等于右边的值。它返回一个布尔值(在C语言中,通常用0表示假,非0表示真)。例如:
● `if (a == 5)`表示判断变量`a`的值是否等于5。
三、位操作的计算方法
1. 步骤一:将数字转换成二进制数字
● 例如,计算5 & 6:
● 5的二进制表示为0101。
● 6的二进制表示为0110。
2. 步骤二:按位与(&)和按位或(|)的计算规则
● 按位与(&)
● 规则:两个相应位都为1,结果才为1,否则为0。
● 计算5 & 6:
● 按位或(|)
● 规则:两个相应位只要有一个为1,结果就为1。
● 计算5 | 6:
四、逻辑操作计算原则
在C语言中,逻辑操作的计算原则是“非0即真”。这意味着在逻辑判断中,任何非零的值都被视为“真”,只有0被视为“假”。
五、逻辑与(&&)操作
1. 示例:5 && 6
● 首先,将操作数转换为逻辑值:
● 5是非0值,所以在逻辑判断中视为“真”。
● 6是非0值,所以在逻辑判断中视为“真”。
● 然后,根据逻辑与(&&)的运算规则:
● 逻辑与(&&)的规则是只有当两个操作数都为“真”时,结果才为“真”。
● 由于5和6对应的逻辑值都是“真”,所以5 && 6的结果为“真”,在C语言中,“真”通常用1表示。
六、逻辑或(||)操作
1. 示例:5 || 6
● 同样,先将操作数转换为逻辑值:
● 5是非0值,所以在逻辑判断中视为“真”。
● 6是非0值,所以在逻辑判断中视为“真”。
● 接着,根据逻辑或(||)的运算规则:
● 逻辑或(||)的规则是只要有一个操作数为“真”,结果就为“真”。
● 因为5和6对应的逻辑值都是“真”,所以5 || 6的结果为“真”,在C语言中用1表示。
总结
在C语言的逻辑操作中:
● 逻辑与(&&):两个操作数都为“真”(非0)时,结果为“真”(1)。
● 逻辑或(||):只要有一个操作数为“真”(非0),结果为“真”(1)。