文章目录
C语言第十天课程笔记
每一天的笔记包含如下内容:
- 当天授课内容安排
- 课堂重点内容笔记
- 课后思考题
1. 内容安排
第一节课: 作用域、变量分类(静态变量、非静态变量,静态函数、非静态函数)
第二节课: 内存分区(代码区、数据区-堆区、全局静态区、字符串常量区、栈区)
第三节课: 内存操作(malloc、free、memset、memcpy、memcmp、memmove)
第四节课: 结构体语法(结构体定义、结构体成员访问、结构体变量定义)
第五节课: 结构体使用注意、结构体作为函数参数
第六节课: typedef、enum
2. 作用域
作用域: 主要探讨标识符(函数名、变量名),这些名字在哪些范围内可以使用。变量名的可见范围.
函数只有文件作用域.
C作用域:
-
文件作用域. 例如: 有些变量在 a.c 定义的,在 b.c 就不能访问.
-
函数作用域. 例如: a函数内定义了一个变量
int number = 10
, b函数中就不能使用 a 函数定义的 number 变量. -
代码块作用域. 例如:
if(条件) { int a= 10; } if(条件) { printf("a = %d\n", a); }
3. 变量分类
- 非静态变量: 全局变量 局部变量
- 非静态全局变量
- 作用域:整个项目中都可以访问
- 内存管理: main 函数执行之前被创建,main 函数执行结束,内存回收.
- 非静态的局部变量
- 作用域: 该变量只在函数内可见
- 内存管理: 局部变量内存函数调用时,分配内存,函数调用结束,回收内存.
- 非静态全局变量
- 静态变量:静态全局变量,静态局部变量
- 静态全局变量
- 作用域: 文件作用域. 只能在当前 .c 文件内访问,在其他 .c 文件中不可访问,不可使用.
- 内存管理: main 函数执行之前创建,main函数执行结束之后回收.
- 静态局部变量
- 作用域: 静态局部变量也是一个局部变量,作用域是当前函数内部.
- 内存管理: main 函数执行之前创建,main函数执行结束之后回收.
- 静态全局变量
非静态变量:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
// 非静态变量: 全局变量 局部变量
// 非静态全局变量
// 作用域:整个项目中都可以访问
// 内存管理: main 函数执行之前被创建,main 函数执行结束,内存回收.
int g_number = 0;
void test01()
{
// 非静态的局部变量
// 作用域: 该变量只在函数内可见
// 内存管理: 局部变量内存函数调用时,分配内存,函数调用结束,回收内存.
int number = 0;
}
静态变量:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
// 静态变量:静态全局变量,静态局部变量
// s_number 静态全局变量
// 作用域: 文件作用域. 只能在当前 .c 文件内访问,在其他 .c 文件中不可访问,不可使用.
// 内存管理: main 函数执行之前创建,main函数执行结束之后回收.
static int s_number = 100;
void test01()
{
// 静态局部变量
// 作用域: 静态局部变量也是一个局部变量,作用域是当前函数内部.
// 内存管理: main 函数执行之前创建,main函数执行结束之后回收.
static int a = 100;
}
// 静态局部变量不会被销毁
void test02()
{
static int a = 10;
++a;
printf("a = %d\n", a);
}
// 1. 允许
// 2. 原因: a 变量是静态变量,整个程序运行期间一直存在.
int* get_number_pointer()
{
static int a = 100;
return &a;
}
// 全局变量、静态变量都是程序执行之前创建,程序执行之后销毁,整个程序运行期间,内存一直存在.
int main()
{
test02();
test02();
test02();
test02();
int *p = get_number_pointer();
system("pause");
return EXIT_SUCCESS;
}
4 函数分类
-
非静态函数
void func() { printf("hello world"); }
非静态函数也叫做全局函数,可以在整个项目任何的 .c 文件中访问.
-
静态函数
static void func() { printf("hello world"); }
静态函数只能在当前文件内访问.
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
// 访问在其他 .c 文件中定义的函数, 有两步:
// 1. 先声明该函数, 告诉编译器这个函数在其他文件中定义.
// 2. 调用函数.
// 如果 func 函数在其他文件中定义为静态函数,则在当前文件中不能使用.
void func(); // 函数声明
int main()
{
func();
system("pause");
return EXIT_SUCCESS;
}
5. 内存分区
编译器会将程序所使用的的内存。代码区、数据区。代码区放在只读区。
为什么代码放在只读区?为什么数据放在可读可写的区域呢?
代码一旦编写完毕,运行过程中不允许任意修改,为了保护代码,在设计的时候,代码就放在只读的内存区域。放代码的区域,叫做代码区。
数据区:
栈区:自动申请,自动释放。
- 函数的参数
- 函数内部定义局部变量
- 可读、可写
全局/静态区: 在程序执行之前分配内存,程序结束之后回收内存。
- 全局变量
- 静态全局变量
- 静态局部变量
- 可读、可写
字符串常量区: 程序运行之前创建,程序运行之后销毁.
- 双引号括起来的字符串会放在字符串常量区.
- 只读,不能修改.
堆区: 手动管理内存申请、手动释放内存。
1. 根据需要申请任意大小的内存。
2. 根据需要选择在合适的时间释放内存。
内存4区: 最主要目的是让我清楚,变量的生命周期。
栈区: 函数开始存储,函数结束释放。
全局静态区: 整个程序运行期间都会存在.
堆区: 手动申请,手动释放,如果申请了没有释放,会造成内存问题:内存泄露。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
// 1. 申请和释放堆内存,需要两个底层函数 malloc free
void test01()
{
// 栈变量
int a = 10;
// 全局静态变量
static int b = 20;
// 向操作系统要了4字节内存,函数返回这个4个字节内存的首地址
// p 指向的内存 1. 手动释放 2. 程序结束之后,统一回收
int* p = (int *)malloc(4);
*p = 30; // 堆内存赋值
printf("*p = %d\n", *p);
// free 函数用于释放堆内存
// 当 free 调用之后,内存就不能再使用了。
// free(p);
}
// 2. 给double类型分配堆空间
void test02()
{
// 在堆上分配了8字节内存,用于存储 double 类型数据
double *d = (double *)malloc(sizeof(double));
// 如何写内存
*d = 3.14;
// 如何读内存
printf("*d = %lf\n", *d);
// 释放内存
free(d);
}
long* create_long()
{
// 动态申请一块long大小的内存
long *l = malloc(sizeof(long));
// 返回
return l;
}
void test03()
{
// 问题: ll 能不能使用? 程序有有什么问题?
long *ll = NULL;
ll = create_long();
free(ll);
}
// 给 long 分配内存,并且赋值为 666, 打印输出
int main()
{
test02();
system("pause");
return EXIT_SUCCESS;
}
内存操作
代码区:不关心.
全局区:项目中所有的文件共享。变量和函数。
extern 类型 变量名;
返回值类型 变量名(参数…);
静态区: 只要函数、变量加上 static 关键字,这些变量和函数只能在当前文件内访问。
字符串常量区: 双引号括起来的字符串。不能修改。
栈区:
-
系统自动管理,不能使用 free 去释放栈区内存。
-
栈区比较小。如果大数据不要放在栈上。当程序运行的时候,栈区大小是固定的。
1. 栈空间占用如果超过最大上限,会出现 Stack Overflow 栈溢出。
-
栈区内存由系统管理,它的内存申请、释放效率非常高的。
int arr1[100000000]; int arr2[100000000]; int arr3[100000000]; int arr4[100000000];
堆区:
- 堆区的内存由开发人员自己申请,自己释放。如果忘记 free, 会出现内存泄露。
- 堆区的内存比较大。真正开发环境下,大量的数据需要放在堆区存储。
- 有些数据,我们需要控制它的生命周期,将数据存储在堆上。
- 堆区相对于栈区,内存管理效率就很低。在项目中,进行优化。内存池。
- 程序一运行,一次性 malloc 一大块内存。
- 当程序需要内存时,找到自己的内存池,使用。
- 用完之后再放到内存池中。
- 减少 malloc free 的次数。
7. 内存操作
操作内存时,不用再区分堆、栈等区别。定义变量的时候,需要区分。
memset : 初始化内存
memcpy: 内存拷贝, 不能出现内存重叠。将内存中的字节拷贝到另外一个内存。处理字符串拷贝,建议还是用 strcpy
memmove: 内存移动. 处理内存重叠现象。效率比 memcpy 低。
memcmp: 内存比较。 strcmp 从开始位置到\0比较。
四个函数都是 mem 开头,#include <memory.h> .
memset例子:
// 1. memset 数组初始化方式、int arr[10] = { 0 }、for 循环、memset
void test01()
{
// 数组初始化方式一
// int arr[10] = {0};
// 数组初始化方式二
//int arr[10];
//for (int i = 0; i < 10; ++i)
//{
// arr[i] = 0;
//}
int arr[10];
// 第一个参数,是初始化内存的首地址, 类型 void *
// 第二个参数,将内存初始化成什么值,类型 int
// 第三个参数,从首地址开始多少个字节,设置为 0
memset(arr, 0, sizeof(arr));
for (int i = 0; i < 10; ++i)
{
printf("%d ", arr[i]);
}
printf("\n");
}
// 将int类型设置为0值
void test02()
{
int a = 412312;
memset(&a, 0, sizeof(int));
printf("a = %d\n", a);
}
void test03()
{
// malloc 返回的指针类型,就是指向首元素类型的指针
char *s = malloc(sizeof(char) * 32);
// 将内存初始化成0
memset(s, 0, 32);
printf("s = %s\n", s);
}
内存拷贝:
// memcpy 不能有内存重叠,如果有内存重叠,不保证一定成功。
// memmove 内存实现交换两个变量的值
void test04()
{
// 内存拷贝
int a = 10;
int b = 20;
// 第一个参数: 目标空间的首地址
// 第二个参数: 源空间的首地址
// 第三个参数: 从源空间拷贝多少字节的数据到目标空间
printf("a = %d, b = %d\n", a, b);
memcpy(&a, &b, 4);
printf("a = %d, b = %d\n", a, b);
}
void test05()
{
int arr[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < 5; ++i)
{
printf("%d ", arr[i]);
}
printf("\n");
// 第一个参数: 目标空间的首地址
// 第二个参数: 源空间的首地址
// 第三个参数: 从源空间拷贝多少字节的数据到目标空间
memmove(arr, arr + 1, sizeof(int) * 4);
for (int i = 0; i < 5; ++i)
{
printf("%d ", arr[i]);
}
}
// 使用 memcpy 交换两个变量的值
void test06()
{
int a = 10;
int b = 20;
printf("a = %d, b = %d\n", a, b);
int temp = 0;
// 将 a 的数据拷贝到 temp 中
memcpy(&temp, &a, 4);
// 将 b 的数据拷贝到 a 中
memcpy(&a, &b, 4);
// 将 temp 的数据拷贝到 b 中
memcpy(&b, &temp, 4);
printf("a = %d, b = %d\n", a, b);
}
内存比较:
// memcmp 内存实现比较两个数组是否相等
// 主要用于判断是否相等, 逐个字节比较
void test07()
{
int a = 10;
int b = 20;
// 第一个参数,参与比较的数据的首地址
// 第二个参数, 参与比较数据的首地址
// 第三个参数,从首地址开始比较的字节数
if (memcmp(&a, &b, 4) == 0)
{
printf("相等!\n");
}
else
{
printf("不相等!\n");
}
}
8. 结构体
结构体定义语法:
先定义类型,再使用类型定义变量.
// 类型定义
struct Person
{
int age;
double salary;
char name[64];
};
// 3. 结构体是一个类型, 类型都拿来定义变量
void test01()
{
struct Person p; // 使用 Person 类型定义出变量叫做 p
p.age = 30;
p.salary = 9999.99;
strcpy(p.name, "Obama");
printf("Name:%s Age:%d Salary%lf\n", p.name, p.age, p.salary);
}
定义类型之后,顺便定义全局变量:
// 2. 员工类型 emp, 员工编号 员工姓名 员工工资 员工电话
struct Emp
{
int emp_no; // 员工编号
char emp_name[64]; // 员工姓名
double emp_salary; // 员工工资
char emp_tele[128]; // 员工电话
}my_emp = {10001, "司马狗剩", 6789.98, "1234567890"}; // 定义类型之后,马上定义全局变量 my_emp
void test03()
{
my_emp.emp_no = 10001;
printf("%d\n", my_emp.emp_no);
}
C语言第十一天课程笔记
每一天的笔记包含如下内容:
- 当天授课内容安排
- 课堂重点内容笔记
- 课后思考题
1. 内容安排
第一节课: 内存操作回顾、结构体训练
第二节课: 结构体嵌套指针、结构体嵌套结构体、结构体嵌套自身类型指针
第三节课: 结构体作为函数参数、结构体数组作为函数参数、main 函数参数
第四节课: 联合体 union、typedef 用法
第五节课: enum枚举、逗号运算符
第六节课: 文件理解
内存分区:代码区、全局静态区、堆区、栈区
我们关注数据分区,主要原因: 我们得了解变量的声明周期,能够知道哪些数据可以用,哪些数据不能用。
分区: 管理方式、大小、效率。
1. 希望一个变量只在函数内存活,应该把变量定义在栈区。
1. 存放哪些变量: 函数形参、函数的局部变量
2. 自动申请、自动释放。
3. 内存大小固定的,容量有限。大数据不要放在栈上。如果栈满了,Stack Overflow, 栈溢出。
4. 效率高。
2. 希望一个变量在整个程序运行期间都存在,并且不需要手动管理。变量应该定义全局静态区。
1. 存放哪些变量: 全局变量、静态变量、字符串常量(只读).
2. 自动分配空间、自动释放空间。
3. 固定大小。
4. 效率高。
3. 希望手动控制变量的生命周期。应该把变量放在堆区。
1. 通过 malloc 函数分配的空间在堆上。
2. 手动申请(malloc)、手动释放(free)。如果 malloc 了,没有 free, 会出现内存泄露问题。底层内存函数。
3. 比较大的。
4. 效率低。优化程序效率的话,减少 malloc free 的次数。
内存操作:
- 内存初始化: memset。
- 内存拷贝: memcpy(效率高、不能处理内存重叠的拷贝)、memmove(效率较低、能够处理内存重叠拷贝)。
- 内存比较: memcmp 比较是否相等.
- memcpy strcpy 区别:
- strcpy(char *dst, char *src)、 memcpy(void *dst, void *src, size_t size)
- strcpy 处理字符串的 \0, memcpy 不处理。
- 字符串拷贝就用 strcpy。
2. 结构体训练
-
定义结构体 student,包括 name、age、score。 实现输入 name、age、score,并打印 name、age、score。
struct Student { // 名字 char name[64]; // 年龄 int age; // 分数 int score; }; void test01() { struct Student student = { "司马小花", 18, 99 }; // 如果 student 是一个普通变量,使用点访问 // 如果 student 是一个指针类型的变量,使用->访问 printf("Name: %s, Age: %d, Score: %d\n", student.name, student.age, student.score); printf("请输入姓名:"); scanf("%s", student.name); printf("请输入年龄:"); // 点的优先级为最高, &优先级是8级 scanf("%d", &student.age); printf("请输入分数:"); scanf("%d", &student.score); printf("Name: %s, Age: %d, Score: %d\n", student.name, student.age, student.score); }
-
定义结构体 account,每个 account 包含账号、身份证号码,姓名,地址,金额。 实现输入各个值,保存到结构体变量中并输出。
3. 结构体嵌套使用注意
-
结构体嵌套指针
struct Person { char *name; // 结构体内部嵌套指针变量 int age; int salary; }; void test01() { // name 指针指向字符串常量 struct Person p1 = { "Obama", 20, 9999 }; // 尝试向 name 指向的内存空间中拷贝字符串数据 // 写入位置 0x01216B30 时发生访问冲突。 // 原因: 1. 写入的空间不是自己申请的。 2. 空间是自己,但是空间是只读的。 strcpy(p1.name, "Trump"); } void test02() { // name 指向的空间是字符串常量区,所以禁止修改 // name 指向自己申请空间 struct Person person = { NULL, 20, 9999 }; // 重新申请一块堆上的内存,name 指向它 person.name = malloc(sizeof(char) * 64); if (NULL == person.name) { return; } // 将 obama 数据拷贝到 name 指向的堆空间中 strcpy(person.name, "obama"); // 打印 Person printf("Name:%s Age:%d Salary:%d\n", person.name, person.age, person.salary); // 释放 name 指向的堆内存 if (person.name != NULL) { free(person.name); person.name = NULL; } }
-
结构体嵌套结构体: 结构体允许嵌套其他类型的结构体变量.
-
struct DrangonWeapon { // 武器攻击力 int attack; // 暴击率 int crit_rate; // 暴击伤害 int crit_damage; }; struct Hero { // 英雄名字 char name[64]; // 攻击力 int damage; // 防御力 int defense; // 血量 int hp; // 魔法 int mp; // 武器 struct DrangonWeapon weapon; }; #if 0 struct Hero { // 英雄名字 char name[64]; // 攻击力 int damage; // 防御力 int defense; // 血量 int hp; // 魔法 int mp; // 武器(匿名结构体) struct { // 武器攻击力 int attack; // 暴击率 int crit_rate; // 暴击伤害 int crit_damage; }weapon; // 定义类型同时定义变量 }; #endif
-
结构体嵌套自身类型指针
结构体允许嵌套本身类型的指针变量。
结构体不允许嵌套本身类型的结构体变量,原因: 无法确定结构体,不能分配内存。
// 结构体不允许嵌套本身类型的结构体变量 #if 0 struct MyStruct { int a; double b; struct MyStruct c; // 编译器不知道 MyStruct 大小 char d; }; #endif struct MyStruct2 { int a; double b; struct MyStruct2 *c; // 无论什么类型的指针,只占4个字节 char d; };
思考:
struct Person { char *name; int age; int salary; }p1 = {NULL, 10, 9999}, *p2 = NULL;
-
第一个问题: p2 是什么类型的?结构类型指针。4个字节。
-
第二个问题:()圆括号 [] 方括号 {}花括号。 花括号在初始化的时候使用。
-
花括号使用的场景:
-
数组定义初始化:
int arr[] = {10, 20, 30}
-
结构体变量定义初始化:
struct Person p = {NULL, 10, 9999}
-
注意:
struct Person p; // 不能使用。只能初始化时候使用 // p 重新赋值操作,此时不能使用花括号 p = {}
-
-
4. 结构体作为函数参数
-
结构体变量作为函数参数
struct Person { char name[64]; int age; }; // 结构体值传递 void print_person(struct Person p) { printf("Name:%s Age:%d\n", p.name, p.age); } // 地址(指针)传递 void print_person_by_pointer(const struct Person *p) { printf("Name:%s Age:%d\n", p->name, p->age); } void test01() { // 对于变量: 值 地址 struct Person person = { "Obama", 33 }; // person 和 p 没有任何关系, p 的修改不会导致 person 改变 // 将结构体 68 字节数据拷贝给 p 变量 print_person(person); // 传递效率不高 // 函数无法修改外部变量 print_person_by_pointer(&person); } void test02() { // 创建结构体指针 struct Person *p_person = NULL; // 创建结构体变量 struct Person person = { "Obama", 33 }; // 1. 要知道变量是指针还是普通变量 // 2. 变量的内存在哪里? // 3. 如果是指针变量,我们得知道指向的是哪里?指向类型? // p_person 栈区, person 栈区 // 指针可以指向 栈变量、堆变量、字符串常量 p_person = &person; // 修改指针指向 p_person = malloc(sizeof(struct Person)); if (p_person != NULL) { free(p_person); p_person = NULL; } } void create_person(struct Person **p) { struct Person *person = malloc(sizeof(struct Person)); strcpy(person->name, "Obama"); person->age = 100; // 通过指针间接赋值的特性,将函数结果返回 *p = person; } void test03() { // 定义指针变量 struct Person *p_person = NULL; // 编写函数,在函数内部为 p_person 分配空间 create_person(&p_person); // 打印输出变量值 printf("Name:%s Age:%d\n", p_person->name, p_person->age); // 释放堆内存 if (p_person != NULL) { free(p_person); p_person = NULL; } }
-
结构体数组作为函数
struct Person { char name[64]; int age; }; void print_person_array(struct Person ps[], int len) { for (int i = 0; i < len; ++i) { printf("Name:%s Age:%d\n", ps[i].name, ps[i].age); } } void test() { // 定义结构体数组 // 对于基本类型,作为函数参数,默认值传递 // 对于数组类型, 作为函数参数,数组名指向首元素的指针 struct Person persons[] = { { "Obama", 45 }, { "Trump", 70 }, { "Polly", 65 }, }; print_person_array(persons, 3); }
5. main 函数参数作用
-
argc 参数作用
- 参数的个数,名字可以修改的。
-
argv 参数作用
- char* 类型数组
- 第一个参数:可执行文件的名字
- 第二个参数之后才是真正要传递给 main 函数的外部数据。
- 通过命令行传递的参数,都会保存在该数组中。
// main 函数的参数,也是由外部传递进来的 // 当你执行 exe 程序的时候,传递进来的。 // 命令行启动程序的时候,可以传递参数 // argc 传递参数的个数 // argv 里面的指针,分别指向了每一个参数 printf("参数的个数是:%d\n", argc); for (int i = 0; i < argc; ++i) { printf("%s\n", argv[i]); // argv[i] 是什么类型? char*类型, char* 类型用于指向字符串 }
-
main 的参数是否能够省略,取决于编译。如果省略,在有些编译就会报错,编译失败。建议的方式:写上main的参数。
-
通过命令传递的参数都是 char* 类型的字符串,无论输入的是什么。
6. 联合体 Union 特性与使用
普通数据定义时,就需要按照一种方式去使用内存。而 Union 允许程序员使用多种方式使用内存数据。
-
union 特性
- union 的数据成员共享内存.
- 每个成员的地址相同。
- 对一个成员赋值会影响其他成员。
- union 类型大小取决于最大成员的大小.
- union 的数据成员共享内存.
-
union 使用
union 可以使用初始化列表默认给第一个成员初始化.
7. typedef 作用与用法
给类型取别名,今后通过别名来定义变量。
-
给普通变量定义别名
typedef int my_int_type; typedef double my_double_type; void test01() { int a = 10; my_int_type b = 10; my_double_type c = 3.14; }
-
给结构体变量定义别名
#if 0 struct Person { char name[64]; int age; }; typedef struct Person MyPerson; #else typedef struct Person { char name[64]; int age; }MyPerson; #endif void test02() { MyPerson person = { "Obama", 56 }; }
-
使用场景: 定义跨平台的数据类型.
// 需求: 定义当前平台支持的最大整型 long long typedef long LLLONG; void test03() { LLLONG a = 10; LLLONG b = 20; }
8. 枚举 enum 作用与使用
-
enum 使用语法
void test01() { // 定义枚举类型, enum Week 类型的名字 enum Week {Mon, Tue, Wed, Thu, Fri, Sat, Sun}; // enum Week { Mon = 100, Tue = 200, Wed = 500, Thu = 800, Fri = 900, Sat = 1000, Sun = 1200 }; // enum Week { Mon, Tue, Wed = 100, Thu, Fri, Sat, Sun }; // 错误:表达式必须为整型常量表达式, 枚举值不能是小数、字符串 // enum Week { Mon = "hello", Tue, Wed = 100, Thu, Fri, Sat, Sun }; // enum Week { Mon = 3.14, Tue, Wed = 100, Thu, Fri, Sat, Sun }; // 使用枚举类型定义变量 enum Week my_week = Mon; my_week = Sun; // 打印枚举值 printf("my_week = %d\n", my_week); }
-
enum 使用注意
// 2. C语言的枚举有什么注意点 void test02() { enum Week { Mon, Tue, Wed, Thu, Fri, Sat, Sun }; enum Week my_week = Mon; // 1. C语言允许给枚举变量赋值其他类型 my_week = 100; // 2. 枚举值具有外层作用域 // int Mon = 200; // 3. 不相同的两枚举类型之间是可以进行比较的,影响代码可读性。 enum Number { One, Two, Three }; if (my_week == One) { printf("相等!\n"); } else { printf("不相等!\n"); } }
9. 逗号运算符语法与作用
逗号运算符:将多个表达式变成一个表达式。
-
比原来的 for 循环更大点的for循环怎么写.
-
能够看得懂别人写的逗号运算符的代码。
-
逗号运算符语法和运算规则
1.1 从左向右, 左边的表达式执行完毕之后,右侧的表达式才会被执行
1.2 逗号运算符最后一个表达式的结果作为整个表达式的结果
void test01() { // 常量表达式 int a = (10, 20, 30); printf("a = %d\n", a); } void test02() { int a = 10; int b = 20; int c = 30; c = (a = b + 10, b = a + 10); // a // b // c printf("a = %d, b = %d, c = %d\n", a, b, c); } void test03() { int a = 10; int b = 20; int c = 30; c = (b + 10, b = a + 10); // a // b // c printf("a = %d, b = %d, c = %d\n", a, b, c); } void test04() { int a = 10; // int b = (a++, a++, a++, a++); int b = (++a, ++a, ++a, ++a); printf("a = %d, b = %d\n", a, b); }
-
-
逗号运算符使用场景
void test05() { for (int i = 0, j = 0; i < 10 || j < 50; ++i, j+=10 ) { printf("i = %d, j = %d\n", i, j); } }
10. 文件理解
-
文件的作用
文件就是数据来源,持久化保存数据。
-
文件的打开路径
绝对路径:从盘符开始记录的路径。
相对路径:相对于当前目录的一个路径。
-
文本文件和二进制文件区别
打开文件分为两种方式: 文本模式、二进制模式打开
文本模式: 换行符。
Mac: \r 作为换行符
Windows: \r\n 作为换行符
Linux: \n 作为换行符
程序中换行符: \n 作为换行符
char s[] = “hello\n”; -> “hello\r”; Mac
char s[] = “hello\n”; -> “hello\r\n”;
char s[] = “hello\n”; -> “hello\n”;
同理: hello\r -> hello\n; “hello\r\n” -> hello\n “hello\n” -> hello\n
当使用文本模式打开一个文件的时,C语言会进行相应的换行符转换。保存文件、读取文件。
二进制读写文件时不做任何转换。 如果数据是 “hello\n” -> “hello\n”
假设 Windows 系统: “hello\r\n” -> “hello\r\n”.
思考:
- 如果是图片文件读写的话?用什么模式?二进制模式。 只要不是为了让你打开看的文件,都可以用二进制模式。
- 写了一篇论文,用文本模式。
-
文件缓冲区
IO : input(输入、读) output(输出、写),相对于程序而言的。
标准IO:标准输入(scanf)-从键盘读, 标准输出(printf)-向屏幕写
文件IO: 文件输入-从文件读,文件输出-向文件写
网络IO: 网络输入-从网络获取数据 网络输出-向网络发送数据
标准IO 也是有缓冲区. 标准输入缓冲区、标准输出缓冲区。行缓冲区。碰到换行符刷新。
文件缓冲区:块缓冲区,缓冲区满的时候,一次性写入数据。 关闭文件,强制刷新缓冲区。
💗💗💗
print("如果文章对你有用,请点个赞呗O(∩_∩)O~")
System.out.println("如果文章对你有用,请点个赞呗O(∩_∩)O~");
cout<<"如果文章对你有用,请点个赞呗O(∩_∩)O~"<<endl;
💗💗💗