C语言学习笔记05:C语言_作用域_变量分类_函数分类_内存分区_ 结构体_联合体_枚举

C语言第十天课程笔记

每一天的笔记包含如下内容:

  1. 当天授课内容安排
  2. 课堂重点内容笔记
  3. 课后思考题

1. 内容安排

第一节课: 作用域、变量分类(静态变量、非静态变量,静态函数、非静态函数)
第二节课: 内存分区(代码区、数据区-堆区、全局静态区、字符串常量区、栈区)
第三节课: 内存操作(malloc、free、memset、memcpy、memcmp、memmove)
第四节课: 结构体语法(结构体定义、结构体成员访问、结构体变量定义)
第五节课: 结构体使用注意、结构体作为函数参数
第六节课: typedef、enum

2. 作用域

作用域: 主要探讨标识符(函数名、变量名),这些名字在哪些范围内可以使用。变量名的可见范围.

函数只有文件作用域.

C作用域:

  1. 文件作用域. 例如: 有些变量在 a.c 定义的,在 b.c 就不能访问.

  2. 函数作用域. 例如: a函数内定义了一个变量 int number = 10, b函数中就不能使用 a 函数定义的 number 变量.

  3. 代码块作用域. 例如:

    if(条件)
    {
      int a= 10;
    }
    
    if(条件)
    {
      printf("a = %d\n", a);
    }
    

3. 变量分类

  1. 非静态变量: 全局变量 局部变量
    1. 非静态全局变量
      1. 作用域:整个项目中都可以访问
      2. 内存管理: main 函数执行之前被创建,main 函数执行结束,内存回收.
    2. 非静态的局部变量
      1. 作用域: 该变量只在函数内可见
      2. 内存管理: 局部变量内存函数调用时,分配内存,函数调用结束,回收内存.
  2. 静态变量:静态全局变量,静态局部变量
    1. 静态全局变量
      1. 作用域: 文件作用域. 只能在当前 .c 文件内访问,在其他 .c 文件中不可访问,不可使用.
      2. 内存管理: main 函数执行之前创建,main函数执行结束之后回收.
    2. 静态局部变量
      1. 作用域: 静态局部变量也是一个局部变量,作用域是当前函数内部.
      2. 内存管理: 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 函数分类

  1. 非静态函数

    void func()
    {
      printf("hello world");
    }
    

    非静态函数也叫做全局函数,可以在整个项目任何的 .c 文件中访问.

  2. 静态函数

    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. 函数内部定义局部变量
  3. 可读、可写

全局/静态区: 在程序执行之前分配内存,程序结束之后回收内存。

  1. 全局变量
  2. 静态全局变量
  3. 静态局部变量
  4. 可读、可写

字符串常量区: 程序运行之前创建,程序运行之后销毁.

  1. 双引号括起来的字符串会放在字符串常量区.
  2. 只读,不能修改.

堆区: 手动管理内存申请、手动释放内存。

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 关键字,这些变量和函数只能在当前文件内访问。

字符串常量区: 双引号括起来的字符串。不能修改。

栈区:

  1. 系统自动管理,不能使用 free 去释放栈区内存。

  2. 栈区比较小。如果大数据不要放在栈上。当程序运行的时候,栈区大小是固定的。

    1. 栈空间占用如果超过最大上限,会出现 Stack Overflow 栈溢出。
    
  3. 栈区内存由系统管理,它的内存申请、释放效率非常高的。

    int arr1[100000000];
    	int arr2[100000000];
    	int arr3[100000000];
    	int arr4[100000000];
    

堆区:

  1. 堆区的内存由开发人员自己申请,自己释放。如果忘记 free, 会出现内存泄露。
  2. 堆区的内存比较大。真正开发环境下,大量的数据需要放在堆区存储。
  3. 有些数据,我们需要控制它的生命周期,将数据存储在堆上。
  4. 堆区相对于栈区,内存管理效率就很低。在项目中,进行优化。内存池。
    1. 程序一运行,一次性 malloc 一大块内存。
    2. 当程序需要内存时,找到自己的内存池,使用。
    3. 用完之后再放到内存池中。
    4. 减少 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. 当天授课内容安排
  2. 课堂重点内容笔记
  3. 课后思考题

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 的次数。

内存操作:

  1. 内存初始化: memset。
  2. 内存拷贝: memcpy(效率高、不能处理内存重叠的拷贝)、memmove(效率较低、能够处理内存重叠拷贝)。
  3. 内存比较: memcmp 比较是否相等.
  4. memcpy strcpy 区别:
    1. strcpy(char *dst, char *src)、 memcpy(void *dst, void *src, size_t size)
    2. strcpy 处理字符串的 \0, memcpy 不处理。
    3. 字符串拷贝就用 strcpy。

2. 结构体训练

  1. 定义结构体 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);
    }
    
  2. 定义结构体 account,每个 account 包含账号、身份证号码,姓名,地址,金额。 实现输入各个值,保存到结构体变量中并输出。

3. 结构体嵌套使用注意

  1. 结构体嵌套指针

    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;
    	}
    }
    
  2. 结构体嵌套结构体: 结构体允许嵌套其他类型的结构体变量.

  3. 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
    
  4. 结构体嵌套自身类型指针

    ​ 结构体允许嵌套本身类型的指针变量。

    ​ 结构体不允许嵌套本身类型的结构体变量,原因: 无法确定结构体,不能分配内存。

    // 结构体不允许嵌套本身类型的结构体变量
    #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;
    
    1. 第一个问题: p2 是什么类型的?结构类型指针。4个字节。

    2. 第二个问题:()圆括号 [] 方括号 {}花括号。 花括号在初始化的时候使用。

    3. 花括号使用的场景:

      1. 数组定义初始化: int arr[] = {10, 20, 30}

      2. 结构体变量定义初始化: struct Person p = {NULL, 10, 9999}

      3. 注意:

        struct Person p;
        // 不能使用。只能初始化时候使用
        // p 重新赋值操作,此时不能使用花括号
        p = {}
        

4. 结构体作为函数参数

  1. 结构体变量作为函数参数

    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;
    	}
    }
    
  2. 结构体数组作为函数

    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 函数参数作用

  1. argc 参数作用

    1. 参数的个数,名字可以修改的。
  2. argv 参数作用

    1. char* 类型数组
    2. 第一个参数:可执行文件的名字
    3. 第二个参数之后才是真正要传递给 main 函数的外部数据。
    4. 通过命令行传递的参数,都会保存在该数组中。
    	// main 函数的参数,也是由外部传递进来的
    	// 当你执行 exe 程序的时候,传递进来的。 
    	// 命令行启动程序的时候,可以传递参数
    	// argc 传递参数的个数
    	// argv 里面的指针,分别指向了每一个参数
    
    	printf("参数的个数是:%d\n", argc);
    	for (int i = 0; i < argc; ++i)
    	{
    		printf("%s\n", argv[i]);  // argv[i] 是什么类型? char*类型, char* 类型用于指向字符串
    	}
    
  3. main 的参数是否能够省略,取决于编译。如果省略,在有些编译就会报错,编译失败。建议的方式:写上main的参数。

  4. 通过命令传递的参数都是 char* 类型的字符串,无论输入的是什么。

6. 联合体 Union 特性与使用

普通数据定义时,就需要按照一种方式去使用内存。而 Union 允许程序员使用多种方式使用内存数据。

  1. union 特性

    1. union 的数据成员共享内存.
      1. 每个成员的地址相同。
      2. 对一个成员赋值会影响其他成员。
    2. union 类型大小取决于最大成员的大小.
  2. union 使用

    union 可以使用初始化列表默认给第一个成员初始化.

7. typedef 作用与用法

给类型取别名,今后通过别名来定义变量。

  1. 给普通变量定义别名

    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;
    }
    
  2. 给结构体变量定义别名

    #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 };
    }
    
  3. 使用场景: 定义跨平台的数据类型.

    // 需求: 定义当前平台支持的最大整型  long long
    
    typedef long LLLONG;
    
    void test03()
    {
    	LLLONG a = 10;
    	LLLONG b = 20;
    }
    

8. 枚举 enum 作用与使用

  1. 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);
    
    }
    
  2. 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. 逗号运算符语法与作用

逗号运算符:将多个表达式变成一个表达式。

  1. 比原来的 for 循环更大点的for循环怎么写.

  2. 能够看得懂别人写的逗号运算符的代码。

  3. 逗号运算符语法和运算规则

    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);
    }
    
  4. 
    
  5. 逗号运算符使用场景

    void test05()
    {
    	for (int i = 0, j = 0; i < 10 || j < 50; ++i, j+=10 )
    	{
    		printf("i = %d, j = %d\n", i, j);
    	}
    }
    

10. 文件理解

  1. 文件的作用

    文件就是数据来源,持久化保存数据。

  2. 文件的打开路径

    绝对路径:从盘符开始记录的路径。

    相对路径:相对于当前目录的一个路径。

  3. 文本文件和二进制文件区别

    打开文件分为两种方式: 文本模式、二进制模式打开

    文本模式: 换行符。

    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”.

    思考:

    1. 如果是图片文件读写的话?用什么模式?二进制模式。 只要不是为了让你打开看的文件,都可以用二进制模式。
    2. 写了一篇论文,用文本模式。

  4. 文件缓冲区

    IO : input(输入、读) output(输出、写),相对于程序而言的。

    标准IO:标准输入(scanf)-从键盘读, 标准输出(printf)-向屏幕写

    文件IO: 文件输入-从文件读,文件输出-向文件写

    网络IO: 网络输入-从网络获取数据 网络输出-向网络发送数据

    标准IO 也是有缓冲区. 标准输入缓冲区、标准输出缓冲区。行缓冲区。碰到换行符刷新。

    文件缓冲区:块缓冲区,缓冲区满的时候,一次性写入数据。 关闭文件,强制刷新缓冲区。

💗💗💗

print("如果文章对你有用,请点个赞呗O(∩_∩)O~")
System.out.println("如果文章对你有用,请点个赞呗O(∩_∩)O~");
cout<<"如果文章对你有用,请点个赞呗O(∩_∩)O~"<<endl;

💗💗💗

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值