栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言学习笔记

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言学习笔记

计算机相关 系统组成
  1. 硬件系统
    1. 主机
      1. 中央处理器(CPU)
        1. 寄存器
        2. 运算器
        3. 控制器
      2. 内存储器
        1. 随机存储器(RAM)
        2. 只读存储器(ROM)
    2. 外部设备(外设)
      1. 输入设备:鼠标、键盘、摄像头等
      2. 输出设备:声卡、显卡等
      3. 外存储器:光盘、硬盘、U盘等
  2. 软件系统
    1. 系统软件
      1. 操作系统
      2. 语言处理系统
      3. 数据库管理系统
      4. 系统服务程序
    2. 应用软件
内存储器与外存储器
  • 内存储器

采用电信号存储数据,速度快,但是断点数据丢失

  • 外存储器

如光盘,采用磁信号存储数据,速度慢,但是可以用来做数据持久化

数据计算时的存储变化

如一个磁盘中的数据:
磁盘 > 磁盘缓存 > 内存 > CPU缓存 > 寄存器 > 运算

寄存器 几个概念
  1. 寄存器是CPU内部的最基本的存储单元
  2. CPU通过总线(数据、地址、控制)来和外部设备打交道
  3. 如果CPU的总线是8位,寄存器也是8位,则这就是个8位的CPU;倘若总线16位,寄存器32位,那么则是一个准32位CPU
  4. 在64位的CPU上运行64位的操作系统,则这是个64位的操作系统,若运行的是32位的操作系统,则这是个32位的操作系统
  5. 64位的软件不能运行在32位的CPU上
寄存器名称
8位CPU16位CPU32位CPU64位CPU
AAXEAXRAX
BBXEBXRBX
CCXECXRCX
DDXEDXRDX
对寄存器的操作

C语言对寄存器做了封装,只能做些简单的操作,如果想进行更深层次的操作,需要使用汇编语言,对此,可以在C语言中嵌套编写汇编语言

#include 

int main(void)
{
	int a, b, c;
	__asm
	{
		mov a, 3 //将3放在a对应的内存中
		mov b, 4 //将4放在b对应的内存中
		mov eax, a //将a放入寄存器中
		add eax, b //将b放入寄存器中,并和其中的a进行加法运算,运算结果(7)依旧在寄存器中
		mov c, eax //将寄存器中的运算结果放入c对应的内存中
	}
	printf("c: %dn", c); //7
	system("pause");
	return 0;
}
Visual Studio相关 VS执行程序时窗口闪退问题的解决方案
  1. 通过system函数
#include 

int main(void)
{
	printf("hello worldn");
	system("pause");
	return 0;
}
  1. 通过修改VS配置
右键项目 > 属性 > 配置属性 > 链接器 > 系统 > 子系统:控制台
代码片段管理
  1. 新建一个,如main函数的


	
		
#1 #1 c语言main函数 Microsoft Corporation Expansion SurroundsWith
expression 要计算的表达式 true #include #include #include #include #include int main(void) { $selected$$end$ system("pause"); return EXIT_SUCCESS; } ]]>


	
		
#2 #2 c++语言main函数 Microsoft Corporation Expansion SurroundsWith
expression 要计算的表达式 true using namespace std; int main() { $selected$$end$ system("pause"); return EXIT_SUCCESS; }]]>
  1. 导入
工具 -> 代码片段管理 -> Visual C++
几个常用快捷键
  1. 格式化代码

ctrl k + ctrl f

  1. 注释

ctrl k + ctrl c

  1. 去除注释

ctrl k + ctrl u

  1. 只编译不运行

ctrl shift b

GCC编译的四个步骤 记事本创建hello.c
#include 

#define PI 3.14159

int main(void)
{
    int a = 1;
    #ifdef PI //判断某个宏是否被定义,若已定义,执行随后的语句
    printf("PI: %fn", PI);
    #endif
    printf("hello cn");
    return 0;
}
步骤一:预处理
  1. 参数:-E
  2. 命令:gcc -E hello.c -o hello.i
  3. -o 选项:表示重命名
  4. 主要工作
  1. 展开头文件

即,会将所有引入的头文件,如此例中的中的stdio.h文件进行展开,需要注意的是,只是展开但是不会对头文件进行检查和校验,即写成也一样会将text.txt进行展开。

gcc -E hello.c -o hello.i -I . //需要使用 -I 选项指定test.txt的所在路径
  1. 展开条件编译

即,在此步骤中,条件编译#ifdef部分代码会被展开执行

#ifdef PI
printf("PI: %fn", PI); 
#endif

执行后的 hello.i

printf("PI: %fn", PI); 
  1. 替换注释

即,在此步骤中,无论单行注释还是多行注释,都会被替换为空白行

  1. 替换宏定义

即,在此步骤中,宏定义会执行替换工作,如例中的宏变量PI,会被替换为其宏值3.14159

//hello.i
printf("PI: %fn", 3.14159);
步骤二:编译
  1. 参数:-S
  2. 命令:gcc -S hello.i -o hello.s
  3. 主要工作
  1. 会逐行检查语法,也因此编译过程是最为耗时的过程
  2. 将C语言转换成汇编指令
步骤三:汇编
  1. 参数:-c
  2. 命令:gcc -c hello.s -o hello.o
  3. 主要工作:将汇编指令翻译为机器码(二进制编码)
步骤四:链接
  1. 参数:无
  2. 命令:
gcc hello.o -o hello.exe //windows系统
gcc hello.o -o hello //linux系统,之后 ./hello 即可调用执行
  1. 主要工作
  1. 数据段合并
  2. 数据地址回填
  3. 库引入

即除了头文件外,还需要引入许多其他系统库,可以使用depends.exe查看所引入的库(将hello.exe拖入其中即可)

常量与变量 常量的定义
#define PI 3.1415926


const int a = 1;
变量的定义
int var = 1;
变量的声明
int var;


extern int a;


#include 
int a;
int main(void)
{
	printf("a: %dn", a); //0
	return 0;
}
#include 

int main(void)
{
    int a;
	printf("a: %dn", a); //windows下直接编译出错
	return 0;
}
#include 
extern int a;
int main(void)
{
	printf("a: %dn", a); //报错,因为extern修饰后无法被提升
	return 0;
}
数据类型 整形 几种整形
  1. short:2字节
  2. int:4字节
  3. long:windows下4字节;linux下32位系统为4字节,64位为8字节
  4. long long:8字节
有符号与无符号
  1. 关键字
  1. signed:有符号

默认就是有符号,所以通常省略不写

  1. unsigned:无符号

即存储的字节中无符号位

  1. 字节数

有符号和无符号的字节数一样

其他几个常用类型 字符类型
  1. 关键字:char
  2. 字节数:1
  3. 常见字符与其对应的数值
字符数值
0
n10
048
A65
a97
  1. char的取值范围
    1. 规定
    00000000: 0; 10000000: -128
    
    1. 有符号
    -2^(8-1) ~ 2^(8-1) - 1; // -128 ~ 127 减1是因为从0开始
    
    1. 无符号
    0 ~ -2^(8) - 1; // 0 ~ 255 减1是因为从0开始
    
浮点类型
  1. 字节数
    1. float:4
    2. double:8
  2. 注意
    float f = 1.1f; //需要加上f后缀,否则默认视为double类型
字符串类型
  • 说明

在C语言中,字符串类型有个很重要的特点,就是,这是字符串的结束标记,且对于用户设置的字符串,系统会自动添加该字符,如定义的 “abc”,实质上是4个字符,即"abc"

printf("abc"); //ab,当遇到第一个时,停止打印
char str[2] = { 'a', 'b' };
printf("str: %sd", str); //str: ab烫烫烫烫烫d

字符串相当于是字符数组的一个特殊形式,即必须以’’结尾

  • 定义
int str[6] = {'h', 'e', 'l', 'l', 'o', ''}; // 如果此处元素个数设置为7个,则第七个元素,即未赋值的元素,默认值为''


int str[] = "hello"; //默认就有''


char str[6] = {0};
  • 接收
#define _CRT_SECURE_NO_WARNINGS
#include 

int main(void)
{
	char str1[6] = { 0 };

	
	scanf("%s", str1);
	printf("res1: %sn", str1); //hello

	char str2[6] = { 0 };
	
	for (size_t i = 0; i < sizeof(str1) / sizeof(str1[0]); i++)
	{
		scanf("%c", &str2[i]);
	}
	printf("res2: %sn", str1); //hello
	return 0;
}
  • 方法
  • gets & fgets
int main(void)
{
	
	char str1[10]; //char str1[11] = {0}; 这样才不会越界
	//gets(str);
	gets_s(str1, 11);
	printf("str1 = %sn", str1); //helloworld

	
	char str2[11];
	printf("str2 = %sn", fgets(str2, sizeof(str2), stdin)); //helloworld
	return 0;
}
  • puts & fputs
int main(void)
{
	
	char str[] = "hello world";
	int ret1 = puts(str);
	printf("ret1 = %dn", ret1);

	
	int ret2 = fputs(str, stdout);
	printf("ret2 = %d", ret2);
	return 0;
}
  • strlen
int main(void)
{
	
	char str1[] = "hello world";
	printf("sizeof: %un", sizeof(str1)); //12
	printf("strlen: %un", strlen(str1)); //11
	char str2[] = "helloworld";
	printf("sizeof: %un", sizeof(str2)); //12
	printf("strlen: %un", strlen(str2)); //5
	return 0;
}
sizeof & printf sizeof
  1. 不是个函数,无需引入头文件,作为一个关键字,用于计算一个数据类型的字节大小
  2. 返回值类型为size_t,通过typedef对unsigned int起的一个别名
  3. 如果传入的是变量值,则括号可以省略
printf
常见格式匹配符说明
%dsigned int 10进制
%osigned int 8进制
%xsigned int 16进制
%hdsigned short
%ldsigned long
%lldsigned long long
%uunsigned int
%huunsigned short
%luunsigned long
%lluunsigned long long
%cchar
%sstring
%ffloat(默认保留6位小数)
%.nffloat(指定保留n位小数)
%m.nffloat( 整数+1(小数点)+小数n = m ,不足默认用空格左填充)
%0m.nffloat( 不足时用0左填充 )
%lfdouble
%%%
示例
#include 
extern int a;
int main(void)
{
	short s = 2;
	int i = 4;
	long l = 8; // or 8L
	long long ll = 8; // or 8LL
	unsigned short us = 2; // or 2u
	unsigned int ui = 4; // or 4u
	unsigned long ul = 8; // or 8Lu
	unsigned long long ull = 8; // or 8LLU

	printf("short: %un", sizeof s); // 2
	printf("int: %un", sizeof(int)); // 4
	printf("long: %un", sizeof(long)); // 4
	printf("long long: %un", sizeof(long long)); // 8
	printf("unsigned short: %un", sizeof(unsigned short)); // 2
	printf("unsigned int: %un", sizeof(unsigned int)); // 4
	printf("unsigned long: %un", sizeof(unsigned long)); // 4
	printf("unsigned long long: %un", sizeof(unsigned long long)); // 8

	return 0;
}
类型限定符 extern

表示 变量/函数 声明,用该关键字声明后的 变量/函数 将不会自动创建内存存储空间

volatile

用于阻止编译器自行优化代码

volatile int flag = 0;
flag = 1; //1
flag = 0; //2
flag = 1; //3
flag = 0; //最后依旧是flag=0,对于这种情况,编译器会进行代码优化,会删除中间变化,即1,2,3步的代码会被删除,而使用了volatile后将不会删除
const

见常量的定义

register

定义一个寄存器变量,即用该关键字定义的变量,将直接存放到寄存器中,但要注意的是不一定保证每次都存放成功,如寄存器已满的情况下将存放失败

寄存器没有和内存一样的地址的概念

int a = 5; //默认将a放入内存中
a + 10; //当使用a时,会先将a放入寄存器中,然后开始执行相关运算
数值存储方式 存储方式

计算机内部以补码的方式存储一个数值

原码
  • 特点
  1. 最高位作为符号位,0正1负
  2. 其他部分为该数值本身绝对值的二进制表示
  • 示例
数值原码
1500001111
-1510001111
000000000
-010000000
反码
  • 特点
  1. 正数的反码同原码
  2. 负数的反码为原码符号位不变,其他位置取反
  • 示例
数值反码
1500001111
-1511110000
000000000
-011111111
补码
  • 特点
  1. 正数的补码同原码
  2. 负数的补码为反码符号位不变,加 1
  • 根据补码求原码
  1. 符号位不变,补码 - 1,然后取反
  2. 符号位不变,取反,再加 1
  • 示例
数值反码
1500001111
-1511110001
000000000
-010000000
示例:43 - 27

+43的补码:00101011
-27的原码:10011011
-27的反码:11100100
-27的补码:11100101

00101011
11100101
——————
00010000 = 16

数据溢出 符号位溢出
  • 说明

对于有符号的数值可能发生,发生后数值的正负符号将改变

  • 示例
char c = 127 + 1;
01111111
00000001
-------------
10000000(补码)
11111111(反码)
10000000(原码) = -128
最高位溢出
  • 说明

对于无符号的数值可能发生,由于最高位丢失,所以发生后数值的差异可能十分大

  • 示例
unsigned char c = 255 + 1;
11111111
00000001
--------------
100000000 -> 溢出后:00000000 = 0
运算符 算数运算符

同java

赋值运算符

同java

比较运算符

同java

逻辑运算符

同java

  • 注意

c中 0、’’ 也表示false

	    int aa = 0;
		if (!aa) 
		{
			printf("aa0 = %d", aa);
		}
		else 
		{
			printf("aa1 = %d", aa);
		}
		//aa0 = 0
三目运算符

同java

逗号运算符
int a = 10, b, c = 30;
int x = (a = 100, b = a * 2, c = c * 10 + a + b);
printf("a = %dn", a); //100
printf("b = %dn", b); //200
printf("c = %dn", c); //600
printf("x = %dn", x); //600,取最后一个结果
return 0;
类型转换 隐式类型转换

注意:如 float、int 进行运算时,都转为链中的交点,即double

强制类型转换

同java

分支语句

同java

循环语句

同java

数组
  1. 相同数据类型的有序(地址)集合
  2. 数组名为第一个元素的地址
  3. 数组空间大小为所有元素大小之和
#include 


int main(void)
{
	int arr[5] = { 4, 6, 1, 2, 90 };
    printf("arr = %xn", arr);
	printf("&arr[0] = %xn", &arr[0]); //   地址为16进制,所以可以用%x来输出打印
	printf("&arr[1] = %xn", &arr[1]); //   
	printf("&arr[2] = %pn", &arr[2]); //   %p为地址的格式匹配符
	printf("&arr[3] = %pn", &arr[3]); //   地址为16进制,所以可以用%x来输出打印
	printf("&arr[4] = %pn", &arr[4]); //   地址为16进制,所以可以用%x来输出打印
	printf("数组元素大小:%un", sizeof(int)); //4
	printf("数组大小:%un", sizeof(arr)); //20,即 (4 * 5)
	
	return 0;						      
}

  1. 初始化一个全为0的数组,需要设置至少一个0元素,否则将是随机数
int arr1[10]; //生成的是10个随机数
int arr2[10] = {0}; //生成全为0的数组,即同java一样,未设置的元素默认值为0
int arr3[3][5] = {0}; //多维数组也一样
//注意
int arr[10];
arr[0] = 1;
//这样做,arr[1 ~9]依旧是个随机数,不是0,因为在声明时已经赋予了初始随机默认值
  1. 可以不指定个数,即数组可以自己计算元素个数
int arr[] = {1, 2, 3}; //会自动统计元素个数 - 3
函数 函数声明
返回值类型 函数名(形参列表); //这就是函数声明
隐式函数声明
 
函数声明案例 
  1. 函数调用前有函数定义:可以正常调用
#include 

void add(int a, int b) 
{
	printf("a + b = %d", a + b); //a + b = 3
}
int main(void)
{
	add(1, 2);
	return 0;
}
  1. 函数调用前无函数定义:自动隐式声明

如果函数恰好返回值为int,则正常调用,否则将抛出重复声明定义的错误

  1. 函数调用前无函数定义,但是有函数声明:可以正常调用
#include 

void add(int a, int b); //函数声明

int main(void)
{
	add(1, 2);
	return 0;
}

void add(int a, int b)
{
	printf("a + b = %d", a + b); //a + b = 3
}

exit函数
  1. 头文件

stdlib

  1. exit()

退出当前程序

  1. return

底层实质上在调用_exit()方法

main函数 不带参的main函数
  • 语法
//写法一
int main(void);

//写法二
int main();
带参的main函数
  • 语法
//写法一
int main(int argc, char * argv[]);

//写法二
int main(int argc, char ** argv);


  • 测试
#include

int main(int argc, char* argv[])
{
	for (size_t i = 0; i < argc; i++)
	{
		printf("argv[%d] = %sn", i, argv[i]); //test.exe aa bb cc dd ee,即至少有一个参数:argv[0] = 程序的相对路径.exe
	}
	return 0;
}
  • 测试一:使用gcc
1. gcc test.c -o test.exe
2. test.exe aa bb cc dd ee
  • 测试二:利用vs
右键项目 -> 属性 -> 调试 -> 命令参数
函数与指针
  • 栈帧

当函数调用时,系统会在stack空间中申请一块栈帧内存区域,可以存放形参与局部变量,以用来提供函数调用


当函数调用时,这块栈帧内存区域也会被自动释放

  • 传值
  • 传递非地址
#include



void swap(int, int);

int main(void)
{
	int m = 1; 
	int n = 2;
	printf("m = %d, n = %dn", m, n); //m = 1, n = 2
	swap(m, n);
	printf("m = %d, n = %dn", m, n); //m = 1, n = 2
	return 0;
}

void swap(int a, int b)
{
	int tmp;
	tmp = a;
	a = b;
	b = tmp;
}
  • 传递地址
#include

void swap(int* a, int* b)
{
	int tmp;
	tmp = *a;
	*a = *b;
	*b = tmp;
}

int main(void)
{
	int m = 1;
	int n = 2;
	printf("m = %d, n = %dn", m, n); //m = 1, n = 2
	swap(&m, &n);
	printf("m = %d, n = %dn", m, n); //m = 2, n = 1
	return 0;
}
  • 传递数组
    当数组作为参数时,传递给函数的不是整个数组,而是数组的首地址,所以此时在函数中对形参进行sizeof计算,得到的是一个指针的大小,而非整个数组的大小,也因此,当需要传递数组时,通常会额外的添加一个形参,用于接收数组的长度
#include

//形参也可以指定长度,即int arr[100],
//形参还可以直接用指针,即int * arr
void bubbleSort(int arr[], int length) 
{
	//printf("sizeof(arr) = %d", sizeof(arr)); //4,求的是一个int类型指针的大小
	int* inner;
	for (size_t i = 0; i < length - 1; i++)
	{
		inner = arr;
		for (size_t j = 0; j < length - i - 1; j++, inner++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp;
				tmp = *inner;
				*inner = *(inner+1);
				*(inner + 1) = tmp;
			}
		}
	}
}

int main(void)
{
	int arr[10] = { 2, 5, 9, 1, 4, 3, 7, 6, 10, 8 };
	int length = sizeof(arr) / sizeof(arr[0]);
	bubbleSort(arr, length);
	for (size_t i = 0; i < length; i++)
	{
		printf("arr[%d] = %d n", i, arr[i]); // 1 2 3 4 5 6 7 8 9 10
	}
	return 0;
}
  • 返回值
  • 返回数组

C语言不允许返回数组

  • 返回指针

不要返回函数内部的局部变量,因为函数结束后栈帧被释放,其中的局部变量将随时被系统分配到其他地方使用

#include

int m = 10000;

int* ret_point_test()
{
	
	return &m;
}

int main(void)
{
	int* p;
	p = ret_point_test();
	printf("p = %dn", *p); //10000,如果返回的是局部变量a,输出的可能是100,那是因为ret_point_test对应的栈帧结束并被标记为释放后,暂时没有被系统分配给其他地方使用
	return 0;
}
多文件编程
  • 含义

将多个含有不同功能.c文件模块,编译到一起,生成一个可执行文件

  • 源文件/add.c
int add(int a, int b)
{
	return a + b;
}
  • 头文件/demo.h
  1. 头文件守卫

用于防止同一头文件在单个CPP/C文件中被重复分析

//方式一:仅在windows系统中可以使用
#pargma once

//方式二:通用方法
#ifndef __DEMO_H__ //对应demo.h
#define __DEMO_H__
    //头文件内容
#endif
  1. 头文件内容
 
  
#ifndef __DEMO_H__
#define __DEMO_H__
//引入头文件
#include 
//函数声明
int add(int a, int b);
//类型定义
//宏定义
#endif
  • 源文件/main.c
#include "demo.h" //自定义头文件的引入用"",在预处理时将会被展开

int main(void)
{
	int res = add(1, 2);
	printf("1 + 2 = %d", res);
	return 0;
}
指针 指针和内存单元
  1. 指针

即内存地址

  1. 内存单元

计算机中内存的最小存储单位,大小为一个字节

  1. 内存单元与指针的关系

每个内存单元都对应有一个惟一编号,该编号就称为该内存单元的地址

  1. 指针变量

即存储指针的变量

指针的定义和使用
  • 关于变量在等号左右两边的不同含义
int m = 10; //m在=左边,表示的是变量名,即对应的内存空间
int n = 20;
n = m; //m在=右边,表示的是m所指向的内存空间中的存储的内容,即10
int a = 10;
int *p = &a; //定义一个整形指针变量p,注意也可以写成 int* p 或 int * p
//等价于 int *p; p = &a;
*p = 20; //指针的使用(指针的解引用/简介引用)
printf("a = %d", a); //20

 int aa = 10;
 int *pp = &aa;
 aa = 20;
 printf("*pp = %d", *pp); /
}
  • const修饰指针的应用场景

常用语函数形参,如

int fputs(const char * str, FILE * stream);
防止待输出的字符串str被中途篡改
指针类型的作用

如有数据:

int a = 0x12345678;

其在内存的存储情况如下:

1. int类型共占有4个字节,对应四个连续的地址0xff00到0xff03,并以首地址0xff00为变量a地址
2. 一个16进制对应4个二进制位,所以两个16进制数就

指针类型的作用:

  1. 决定了指针从存储地址开始的读取的字节数,如int一次读取4个字节
  2. 决定了指针变量做加/减1运算时偏移的字节数,如int一次偏移4个字节
int main(void)
{
	int a = 0x12345678;
	int* p1 = &a;
	short* p2 = &a;
	char* p3 = &a;
	printf("*p1 = %pn", *p1); //12345678
	printf("*p2 = %pn", *p2); //00005678
	printf("*p3 = %pn", *p3); //00000078
    printf("&a = %pn", &a); //00FF0000
	printf("p1 = %pn", p1); //00FF0000
	printf("p1 + 1 = %pn", p1 + 1); //00FF0004
	printf("p2 = %pn", p2); //00FF0000
	printf("p2 + 1 = %pn", p2 + 1); //00FF0002
	return 0;
}
int main(void)
{
	int arr[] = {1, 2, 3, 4, 5, 6, 7, 8 ,9};
	int* p = &arr;
	int length = sizeof(arr) / sizeof(arr[0]);
	for (size_t i = 0; i < length; i++, p++)
	{
		printf("arr[%d] = %dn", i, *p);
	}
	printf("p - arr = %d", p - arr); //9,地址相减得到的是元素的个数
	return 0;
}
指针的算术运算
  • 指针与整数的乘除取余操作:error,即无法进行
  • 指针与整数的加减操作
  1. 普通指针变量的加减
char * p; p + 1; //表示偏移对应的字节数
  1. 在数组中的加减
int arr[] = {1, 3, 5};
int *p = arr;
p + 1; //表示偏移的元素个数(本质还是字节数)
  • &数组名 + 1
int main(void)
{
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8 ,9 };
	printf("arr      = %pn", arr);
	printf("&arr[0]  = %pn", &arr[0]);
	printf("arr + 1  = %pn", arr + 1);
	printf("&arr[1]  = %pn", &arr[1]);
	printf("&arr     = %pn", &arr); //数组首元素地址
	printf("&arr + 1 = %pn", &arr + 1); //偏移一个数组的大小,即偏移(数组元素个数*sizeof(数组类型))的大小
	
	return 0;
}
  • 指针之间的算术运算
  1. 加、乘除、取余:error,即禁止操作
  2. 减法
int main(void)
{
	//1.对于普通变量,无实际意义
	int a = 10;
	int b = 20;
	int* pa = &a;
	int* pb = &b;
	printf("pa = %pn", pa); //012FF77C
	printf("pb = %pn", pb); //012FF770
	printf("pa - pb = %pn", pa - pb); //00000003,即 (C-0)/sizeof(int)

	//2.对于数组,相当于二者对应元素的下标之差
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int* p = &arr[3];
	int* q = &arr[6];
	printf("q - p = %d", q - p); //3
	return 0;
}
指针的比较运算
  • 地址之间可以进行比较大小(> < =)
  • 普通变量的比较没有意义,主要用于数组,可以将地址视为数字,即比较这个数字的大小,大的表示在更高位
  • p != NULL,即常用语是否为空指针的比较
指针实现的strlen函数
int mystrlen(const char str[]);

int main(void)
{
	char str[] = "hello world";
	int len = mystrlen(str);
	printf("length is %dn", len);
	return 0;
}

int mystrlen(const char str[]) {
	char* p = str;
	while(*p != '') 
	{
		p++;
	}
	return p - str;
}
指针数组
  1. 指针数组本质就是一个二级指针:**arr
int main(void)
{
	int a = 10;
	int b = 20;
	int c = 30;
	int* pa = &a;
	int* pb = &b;
	int* pc = &c;
	int* arr[] = { pa, pb, pc };
	printf("*(arr[0]) = %dn", *(arr[0])); //10
	printf("*(*(arr + 0)) = %dn", *(*(arr + 0))); //10
	printf("**arr = %dn", **arr); //10
	return 0;
}
  1. 二维数组本质也是个二级指针
int main(void)
{
	int a[] = { 10 };
	int b[] = { 20 };
	int c[] = { 30 };
	int* arr[] = { a, b, c };
	printf("arr[0][0] = %dn", arr[0][0]); //10
	printf("*((*(arr + 0) + 0)) = %dn", *((*(arr + 0) + 0))); //10
	printf("**arr = %dn", **arr); //10
	return 0;
}
多级指针
int main(void)
{
	int a = 10;
	int* p = &a; //一级指针是变量地址
	//int **p2 = &(&a) 不允许直接跳级定义
	int** p2 = &p; //二级指针是一级指针地址
	int*** p3 = &p2; //三级指针是二级指针地址
	p3 == &p2;
	*p3 == p2 == &p; /
  • 作用

字符串分割,函数会找到str中首次出现的delim中的任意一个字符,并将原串,即str中该字符替换为,然后返回str的首地址,这样接得到str首地址到 的这部分 字符串了

  • 示例
#define _CRT_SECURE_NO_WARNINGS

#include
#include

int main(void)
{
	//strtoc("abc.com", "."); 不可以,因为这样直接传入的"abc.com"是个常量
	char str[] = "abc.com.cn";
	char* res = strtok(str, ".");
	printf("res = %sn", res); //abc
	return 0;
}
  • 进阶

“abc de f.com%100.cn p”,获取abc de f com 100 cn p

#define _CRT_SECURE_NO_WARNINGS

#include
#include

int main(void)
{
	char* res[50] = {NULL}; //需要初始化,否则是野指针,值为默认的随机数字,而该数字对应的地址上可能是禁止访问的,所以在读取时会报错
	char** p = &res;

	char str[] = "abc de f.com%100.cn p";
	char* ret = strtok(str, ". %"); //第一次切分需要传入str
	*p = ret;
	p++;

	while (NULL != ret)
	{
		ret = strtok(NULL, ". %");
		*p = ret;
		p++;
	}
	
	for (size_t i = 0; i < 10; i++)
	{
		if (NULL != res[i])
		{
			printf("res[%d] = %sn", i, res[i]); 
		}
	}
	
	return 0;
}
atoi & atof & atol
  • 语法与作用
int atoi(const char* nptr); //将字符串转为int
float atof(const char* nptr); //将字符串转为float
long atol(const char* nptr); //将字符串转为long
  • 示例
#include
#include
#include

int main(void)
{
	char* str1 = "123";
	int num1 = atoi(str1);
	printf("num1 = %dn", num1);

	char* str2 = "0.123f";
	float num2 = atof(str2);
	printf("num2 = %.2lfn", num2); //注意要加上头文件stdlib.h,否则0.00

	char* str3 = "123L";
	long num3 = atol(str3);
	printf("num3 = %ldn", num3);

	return 0;
}
内存管理 作用域

c语言将变量的作用域分为三种

  1. 代码块作用域(即{}范围内的)
  2. 函数作用域
  3. 文件作用域
局部变量
  • 说明

也叫auto自动变量(auto可写可不写),一般情况下,代码块{}内部定义的变量都是自动变量

  • 特点
  1. 在一个函数内定义,只在该函数范围内有效
  2. 在复合语句中定义,只在该复合语句中有效
  3. 随着函数调用的结束或复合语句的结束,局部变量的生命周期也随之结束
  4. 如果没有被赋予初值,系统会赋予一个随机值
  • 生命周期

从定义变量开始,到所属函数对应的栈帧被释放为止

全局变量
  • 特点
  1. 定义在函数外,可以被本(.c)文件,以及其他文件中的函数共同使用(注意,如果是其他文件中的函数调用,需要使用extern关键字声明)
  2. 全局变量的生命周期同程序的运行周期
  3. 不同文件的全局变量也不可以重名
  4. 如果没有被赋予初值,int类型的赋予0值,其他类型类似
  • 生命周期

从程序执行开始(在main函数之前),到程序执行结束为止,即整个程序执行期间

static局部变量
  • 说明

作用域依旧是局部代码块,但是只会定义一次,且定义的位置是在全局位置,通常用来做计数器

  • 示例
#include

void test()
{
	static int n = 0;
	printf("n = %dn", ++n); //1 2 3 4 5
}

int main(void)
{
	for (size_t i = 0; i < 5; i++)
	{
		test();
	}
	return 0;
}
  • 生命周期

从程序执行开始(在main函数之前),到程序执行结束为止,即整个程序执行期间

static全局变量

static int a = 10;
也是定义在函数外,但是经过static关键字修饰后,本文件中的全局变量a,只限制于在本文件中使用,即在其他文件中无法通过extern关键字进行声明与使用。

  • 生命周期

从程序执行开始(在main函数之前),到程序执行结束为止,即整个程序执行期间

extern全局变量声明

extern int a;
只是声明一个变量,该变量(a)已经在别的文件中定义过了,这里只是声明的作用

全局函数与静态函数
  • 说明

在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态,意味着该函数只能在定义这个函数的文件中使用,在其他文件中不能调用,即使在其他文件中声明这个函数。

  • 生命周期

无论是全局函数还是静态函数,也一样都是从程序执行开始(在main函数之前),到程序执行结束为止,即整个程序执行期间

内存4区模型 windows

linux

.text 和 .rodata 链接后会合并,赋予只读权限
.data 和 .bss 链接后会合并,赋予读写权限

常用函数 memset
  • 说明
void *memset(void *s,int c,size_t n)

  • 示例
#include
#include

int main(void)
{
	//对应int数组需要注意
	int arr[] = { 1, 2, 3 };
	memset(arr, 1, 3); 
	
	for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("arr[%d] = %d n", i, arr[i]);
		
	}

	//一般用于字符数组(字符串)
	char str1[] = { 'a', 'b', 'c', '' };
	memset(str1, 'i', 2);
	puts(str1); //iic

	char str2[] = "abc";
	memset(str2, 'u', sizeof(str2) - 1); //避免将最后的''覆盖
	printf("%s", str2); //uuu
	return 0;
}

memcpy
  • 说明
void *memcpy(void *str1, const void *str2, size_t n)

  • 示例
#define _CRT_SECURE_NO_WARNINGS
#include
#include

int main(void)
{
	char str[] = "abcdef";
	printf("sizeof(str) = %lu n", sizeof(str)); //8
	char cpy1[10];
	char cpy2[10];
	strncpy(cpy1, str, sizeof(str)); //strcpy遇到将停止复制
	memcpy(cpy2, str, sizeof(str)); //memcpy不受的影响
	printf("strnpy: %sn", cpy1 + strlen("abc") + 1); //
	printf("memcpy: %sn", cpy2 + strlen("abc") + 1); //def
	
	int a[5] = { 1, 2, 3, 4, 5 };
	int b[5];
	memcpy(b, a, sizeof(a));
	return 0;

	//注意不要出现内存重叠
	//如 memcpy(&a[2], a, 5 * sizeof(int))
}
memmove
  • 说明

参见memcpy,用于补足内存重叠情形

  • 示例
#define _CRT_SECURE_NO_WARNINGS
#include

int main(void)
{
	int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	memmove(&a[2], a, 5 * sizeof(int));
	for (size_t i = 0; i < sizeof(a)/sizeof(a[0]); i++)
	{
		printf("a[%d] = %dn", i, a[i]);
		
	}
	return 0;
}

memcmp
  • 说明

用于两个值的比较(对于数组从第一个元素逐个向后比较)

  • 示例
#define _CRT_SECURE_NO_WARNINGS
#include

int main(void)
{
	int a[] = { 1, 2, 3, 4, 5 };
	int b[] = { 1, 3, 2 };
	int res = memcmp(a, b);
	if (res > 0)
	{
		printf("a > b");
	}
	else if (res < 0)
	{
		printf("a < b");
	}
	else {
		printf("a == b");
	}
	//a < b
	return 0;
}
堆空间的使用 malloc & free
  • 说明
void* malloc(size_t size); //用于申请堆内存,size为指定的要申请的内存大小(字节数),返回值为申请到的内存地址,一般当做数组来使用
void free(void* ptr); //用于释放申请的堆内存,即释放malloc申请的内存,需要注意的是释放之后这部分内存并不会立即失效,所以往往还会进行赋NULL操作
  • 示例
#include

int main(void)
{
	//int arr[100000] = { 0 }; //超出栈内存容量,直接无法启动,所以此时需要使用堆内存
	int* p = (int*)malloc(10 * sizeof(int)); //申请
    int* tmp = p; //用于free,防止p地址执行了p++等操作,导致地址变化
	for (size_t i = 0; i < 10; i++) //使用:赋值
	{
		p[i] = i + 10;
	}
	for (size_t i = 0; i < 10; i++) //使用:遍历
	{
		printf("p[%d] = %dn", i, *(p+i));
	}
	free(tmp); //释放,注意不要再释放前对p进行++等操作,因为free要释放的必须是malloc申请的地址
    tmp = NULL; //立即失效
	return 0;
}
二级指针malloc空间
#include

int main(void)
{
	//1.申请外层空间
	int** p = malloc(sizeof(int*) * 3);
	//2.申请内存空间
	for (size_t i = 0; i < 3; i++)
	{
		p[i] = malloc(sizeof(int) * 5);
	}
	//3.写入数据
	for (size_t i = 0; i < 3; i++)
	{
		for (size_t j = 0; j < 5; j++)
		{
			p[i][j] = i + j;
		}
	}
	//4.读取数据
	for (size_t i = 0; i < 3; i++)
	{
		for (size_t j = 0; j < 5; j++)
		{
			printf("p[%d][%d] = %dn", i, j, *(*(p + i) + j));
		}
		printf("n");
	}
	//5.释放内层空间
	for (size_t i = 0; i < 3; i++)
	{
		free(p[i]);
		p[i] = NULL;
	}
	//6.释放外层空间
	free(p);
	p = NULL;
	return 0;
}
复杂数据类型 结构体 结构体说明
struct Student
{
    int age;
    char name[100];
    int score;
};

结构体示例
#define _CRT_SECURE_NO_WARNINGS
#include
#include

struct Student
{
	char name[100];
	int age;
	int score;
};
struct Student2
{
    char name[100];
	int age;
	int score;
} s1 = {"zhangsan", 18, 100}, s2; //可以同时定义(多个)变量,并进行初始化
struct //匿名结构体,只能使用此时定义的变量,不能再创建变量
{
	char name[100];
	int age;
	int score;
} s3, s4;

int main(void)
{
	
	struct Student stu;

	
	struct Student stu1 = { "张三", 18, 100 };

	
	struct Student stu11;
	stu11.age = 18; //或:(&stu11) -> age = 19
	//stu11.name = "李四"; 数组是常量,不能直接赋值
	strcpy(stu11.name, "李四"); //或:strcpy((&stu11) -> name, "李四")
	stu11.score = 100; //或:(&stu11) -> score = 100

	struct Student *stu12;
	stu12 = &stu; //注意,如果是野指针,会报错
	stu12->age = 19; //或:(*stu12).age = 19
	strcpy(stu12->name, "王五"); //或:strcpy((*stu12).name, "王五")
	stu12->score = 100; //或:(*stu12).score = 100;

    
    struct Student stus999;
    stus999 = stu11; //同 int a = 1; int b; b = a;

	return 0;
}

结构体数组
#define _CRT_SECURE_NO_WARNINGS
#include
#include

struct Stu
{
	char name[100];
	int age;
};

int main(void)
{
	//定义时初始化
	struct Stu stus1[5] =
	{ //内部的花括号{}可以省略
		{"zhangsan", 1},
		{"lisi", 2},
		{"wangwu", 3},
		{"zhaoliu", 4},
		{"xiaoming", 5}
	};

	//多种赋值方式
	struct Stu stus2[5];
	strcpy(stus2[0].name, "zhangsan");
	stus2[0].age = 12;

	strcpy((stus2 + 1)->name, "lisi");
	(stus2 + 1)->age = 19;

	strcpy((*(stus2 + 2)).name, "wangwu");
	(*(stus2 + 2)).age = 20;

	struct Stu* p = stus2;
	strcpy((p + 3)->name, "zhaoliu");
	(p + 3)->age = 21;

	strcpy(p[4].name, "xiaoming");
	p[4].age = 21;

	for (size_t i = 0; i < sizeof(stus2)/sizeof(stus2[0]); i++)
    {
		printf("name=%s, age=%d n", stus2[i].name, stus2[i].age);
	}
	return 0;
}
结构体嵌套结构体
#define _CRT_SECURE_NO_WARNINGS
#include
#include

struct baseInfo
{
	char name[100];
	int age;
};

struct Player
{
	struct baseInfo info;
	int score;
};

int main(void)
{
	struct Player player1;
	player1.info.age = 20;
	strcpy(player1.info.name, "zhangsan");
	player1.score = 100;

	struct Player* player2 = &player1;
	strcpy(player2->info.name, "lisi");
	player2->info.age = 21;
	player2->score = 90;

	struct Player player3 = { "wangwu", 22, 91 }; //会自动按顺序赋值

	printf("%s, %d, %d", player2->info.name, player2->info.age, player2->score);
	return 0;
}
结构体指针成员
  • 问题
#define _CRT_SECURE_NO_WARNINGS
#include
#include

struct A
{
	char* name;
};

int main(void)
{
	struct A a;
	strcpy(a.name, "abc"); //error:name是个野指针,无法接受拷贝数据
	return 0;
}
  • 方案一:指向文字常量区(data区)
int main(void)
{
	struct A a;
	a.name = "abc"; //字符串实质就是其内存首地址,所以相当于:&变量
	return 0;
}
  • 方案二:指向栈区内存
int main(void)
{
	struct A a;
	char temp[100];
	a.name = temp;
	strcpy(a.name, "abc");
	puts(temp); //abc
	return 0;
}
  • 方案三:指向堆区内存
#include
int main(void)
{
	struct A a;
	a.name = (char*)malloc((strlen("abc") + 1) * sizeof(char));
	if (a.name != NULL)
	{
		strcpy(a.name, "abc");
		puts(a.name); //abc
	}
    if (a.name != NULL)
    {
        free(a.name);
        a.name = NULL;
    }
	return 0;
}
  • 指针结构体嵌套指针成员
int main(void)
{
	struct A *a;
	//分配内存
	a = (struct A*)malloc(sizeof(struct A));
	a->name = (char*)malloc((strlen("abc") + 1) * sizeof(char));
	//赋值
	strcpy((*a).name, "abc");
	//打印
	printf("name=%sn", a->name);
	//释放:先内部
	if (a->name != NULL)
	{
		free(a->name);
		a->name = NULL;
	}
	//释放:再外部
	if (a != NULL)
	{
		free(a);
		a = NULL;
	}
	return 0;
}
结构体嵌套二级指针
#define _CRT_SECURE_NO_WARNINGS
#include
#include

typedef struct Teacher
{
	char* name;
	char** students;
} Teacher;

void allocateSpace(Teacher*** teachers)
{
	if (NULL == teachers) return;
	//1.分配老师数组
	Teacher** tArray = malloc(sizeof(Teacher*) * 3);
	//2.分配老师
	for (size_t i = 0; i < 3; i++)
	{
		tArray[i] = malloc(sizeof(Teacher));
		//3.分配老师姓名并赋值
		tArray[i]->name = malloc(sizeof(char) * 64);
		sprintf(tArray[i]->name, "Teacher_%d", i + 1);
		//4.分配学生数组
		tArray[i]->students = malloc(sizeof(char*) * 5);
		for (size_t j = 0; j < 5; j++)
		{
			//5.分配学生名字并赋值
			(tArray[i]->students)[j] = malloc(sizeof(char) * 64);
			sprintf((tArray[i]->students)[j], "%s_Student_%d", tArray[i]->name, j + 1);
		}
	}
	*teachers = tArray;
}

void showSpace(Teacher** teachers)
{
	if (NULL == teachers) return;
	for (size_t i = 0; i < 3; i++)
	{
		puts(teachers[i]->name);
		for (size_t j = 0; j < 5; j++)
		{
			printf("     %sn", teachers[i]->students[j]);
		}
	}
}

void freeSpace(Teacher** teachers)
{
	if (NULL == teachers) return;
	for (size_t i = 0; i < 3; i++)
	{
		//1.释放老师名字
		if (NULL != teachers[i]->name)
		{
			free(teachers[i]->name);
			teachers[i]->name = NULL;
		}
		for (size_t j = 0; j < 5; j++)
		{
			//2.释放学生姓名
			if (NULL != teachers[i]->students[j])
			{
				free(teachers[i]->students[j]);
				teachers[i]->students[j] = NULL;
			}
		}
		//3.释放学生数组
		if (NULL != teachers[i]->students)
		{
			free(teachers[i]->students);
			teachers[i]->students = NULL;
		}
		//4.释放老师
		if (NULL != teachers[i])
		{
			free(teachers[i]);
			teachers[i] = NULL;
		}
	}
	//5.释放老师数组
	if (NULL != teachers)
	{
		free(teachers);
		teachers = NULL;
	}
}

int main(void)
{
	Teacher** teachers = NULL;

	//分配内存
	allocateSpace(&teachers);

	//展示数据
	showSpace(teachers);

	//释放内存
	freeSpace(teachers);

	return 0;
}
结构体拷贝
  • 系统默认的浅拷贝
#include
#include

typedef struct P
{
	char name[64];
	int age;
} P;

int main(void)
{
	P p1 = { "zhangsan", 18 };
	P p2 = { "lisi", 20 };
	
	p1 = p2;
	printf("p1: name=%s, age=%d", p1.name, p1.age);
	return 0;
}
  • 系统浅拷贝所引起的问题
#define _CRT_SECURE_NO_WARNINGS
#include
#include

typedef struct P
{
	char* name;
	int age;
} P;

int main(void)
{
	P p1;
	P p2;

	p1.name = malloc(sizeof(char) * 64);
	strcpy(p1.name, "zhangsan");
	p1.age = 18;
	p2.name = malloc(sizeof(char) * 128);
	strcpy(p2.name, "lisi");
	p2.age = 20;
	
	p1 = p2;

	if (p1.name != NULL)
	{
		free(p1.name);
		p1.name = NULL;
	}

	if (p2.name != NULL)
	{
		
		free(p2.name);
		p2.name = NULL;
	}

	printf("p1: name=%s, age=%d", p1.name, p1.age);
	return 0;
}

  • 手动深拷贝的解决方案
 
 
结构体字节对齐
  • 字节对齐使得实际占用字节数往往会偏大
#include

struct Demo1
{
	int a; 
	char b;
};

int main(void)
{
	printf("Demo1: %lu", sizeof(struct Demo1)); //8 【理论上是:4(int) + 1(char) = 5】
	return 0;
}
  • 字节对齐的原因
  1. 平台原因(移植原因)

不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

  1. 性能原因(空间换时间)

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问

如上图,在32位操作系统中,数据总线是32位,所以一次读写4字节,如果未对齐,则对于其中的i(int),需要读取两次才能确定数值,而对齐后只需一次

  • 对齐规则
  1. 第一个成员偏移量为0
  2. 其余成员偏移量为对齐数整数倍
  3. 对齐数 = min ("成员中字节数最大者字节数", #pragma pack(指定的对齐数))
  4. 如果有嵌套结构体,对齐数取内外层结构体中的较大者

  • 示例一
#include

struct Demo1
{
	short a;
	char b;
};

int main(void)
{
	
	printf("Demo1: %lu", sizeof(struct Demo1)); //4
	return 0;
}
  • 示例二
#include

struct Demo2
{
	short a;
	double b;
	char c[2];
};

int main(void)
{
	
	printf("Demo2: %lu", sizeof(struct Demo2)); //24
	return 0;
}
  • 示例三:嵌套结构体
#include

int main(void)
{
	typedef struct Demo1
	{
		double a1;
		short b1;
	} Demo1;

	struct
	{
		char a2;
		Demo1 b2;
		int c2;
	} Demo2;
	
	printf("Demo2: %lu", sizeof(Demo2)); //32
	return 0;
}
  • 设置对齐数
  1. 对齐数 > 最大成员字节数

取最大成员字节数为实质对齐数

#include
#pragma pack(8)

int main(void)
{
	struct
	{
		char a;
		int b;
	} Demo;
	printf("Demo: %lu", sizeof(Demo)); //8
	return 0;
}
  1. 对齐数 < 最大成员字节数

字节数超过对齐数的成员计算偏移量时以对齐数进行计算

#include
#pragma pack(2) //设置对齐数为2

int main(void)
{
	struct
	{
		char a;
		int b;
	} Demo;
	// 对齐数 = 2
	// a: 1 * 0 = 0
	// b: 2 * 1 = 2
	//
	// a * 
	// b b
	// b b
	printf("Demo: %lu", sizeof(Demo)); //6
	return 0;
}
结构体位段
  • 说明

结构体中允许存在位段、无名字段以及字对齐所需的填充字段。这些都是通过在字段的声明后面加一个冒号以及一个表示字段位长的整数来实现。这些冒号后的整数规定了成员所占的位数

  • 示例
#include

int main(void)
{
	struct
	{
		char a : 4;
		int b : 8;
	} Demo;
	Demo.a = 0xf0; //会截断,只取前4位
	Demo.b = 257; //会截断,只取前8位,即一字节
	
	
	printf("a = %dn", Demo.a); //0
	printf("b = %dn", Demo.b); //1
	return 0;
}
  • 结构体存储压缩

如果结构体中相连且相同类型的成员指定了位段,且位段总和小于其原始类型字节数,则会视情况将这些成员压缩到一个字节类型空间中

#include

int main(void)
{
	struct
	{
		int a : 4;
		int b : 8;
	} Demo;
	
	printf("Demo: %lu", sizeof(Demo)); //4,压缩到了一个int中
	return 0;
}
共用体(联合体)
  • 说明
  1. 所有成员公用相同的内存地址
  2. 大小为成员最大的字节数
  3. 由于公用内存,所以改动一个成员,其余成员将受影响
  • 示例
#include

int main(void)
{
	union
	{
		unsigned char a;
		unsigned int b;
		unsigned short c;
	} Demo;
	printf("大小为成员中字节数最大的:%lun", sizeof(Demo)); //4
	Demo.b = 0x11223344; //首地址为低位,从低位开始赋值
	printf("a=%xn", Demo.a); //44
	printf("b=%xn", Demo.b); //11223344
	printf("c=%xn", Demo.c); //3344
	Demo.a = 0xff;
	printf("a=%xn", Demo.a); //ff
	printf("b=%xn", Demo.b); //112233ff
	printf("c=%xn", Demo.c); //33ff
	return 0;
}
枚举
#include

enum Color
{
	GREEN,
	YELLOW,
	RED=10,
	BLUE,
	PINK
};

int main(void)
{
	
	enum Color flag = GREEN;
	enum Color flag2 = 1; //相当于YELLOW,一般不会这么写

	
	printf("%d, %d, %d, %d, %d", GREEN, YELLOW, RED, BLUE, PINK); //0, 1, 10, 11, 12
	return 0;
}
文件处理 概述
typedef struct
{
	short level; //缓冲区状态(满/空的程度)
	unsigned flags; //文件状态标志
	char fd; //文件描述符
	unsigned char hold; //如无缓冲区不读取字符
	short bsize; //缓冲区大小
	unsigned char * buffer; //数据缓冲区的位置
	unsigned ar; //指针,指向当前的位置
	unsigned istemp; //临时文件,指示器
	short token; //用于有效性的检查
} FILE;

FILE * fp;
  1. 对于各平台,结构体FILE内部的成员变量不尽一致,但是结构体变量名称均为FILE
  2. 只要fp调用了fopen()函数,该函数就会在堆区申请空间并将地址返回给fp
  3. 并非是fp关联文件,而是内部成员保存了文件的相关信息
  4. 不要直接操作fp指针,而是通过相关函数,函数的调用会自动调整fp中成员的相关值
  5. 文件描述符,每打开一个文件对应就给这个文件一个int值,用于标识,在linux中可以使用ulimit -a命令,其中open files就是描述符取值范围,其中0(stdin), 1(stdout), 2(stderr)三个值默认为系统占用
三大特殊文件指针

C语言中有三个特殊的文件指针由系统默认打开,无需用户定义即可直接使用

  1. stdin

标准输入,默认为当前终端(键盘),scanf, getchar等函数默认从此终端获得数据

  1. stdout

标准输出,默认为当前终端(屏幕),printf, puts等函数默认输出数据到此终端

  1. stderr

标准出错,默认为当前终端(屏幕),perror函数默认输出数据到此终端

#define _CRT_SECURE_NO_WARNINGS
#include

int main(void)
{
	int a;
	printf("请输入:");
	scanf("%d", &a);
	printf("a = %dn", a);
	fclose(stdin); //关闭标准输入
	scanf("%d", &a); //不会 阻塞并让用户输入
	perror("stdin error"); //输出报错原因(只能用于库函数)

	fclose(stdout); //关闭标准输出
	printf("========="); //不会输出
	perror("stdout error: ");

	fclose(stderr); //关闭标准出错
	perror("stderr error: "); //不会输出

	return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include

int main(void)
{
	printf("aaaaaaa");
	_close(1); //1对应标准输出设备
	int fd = _open("test.txt", O_WRONLY, 0777);
	printf("fd = %dn", fd); //1,说明此时将1分配给了test.txt,而不再是标准输出
	printf("bbbbbbb"); //将写入到test.txt
	return 0;
}

VS环境下的相对路径
  1. 如果是编译运行,则相对位置起点是项目名称.vcxproj所在路径;
  2. 如果是直接双击xxx.exe,则是xxx.exe所在路径
常用函数 fopen & fclose
  • 函数声明
FILE * fopen(const char* filename, const char * mode);


int fclose(FILE * strean);
//返回值:成功0,失败-1
  • 示例
#define _CRT_SECURE_NO_WARNINGS
#include
#include

int main(void)
{
	FILE* fp = fopen("test.txt", "r");
	if (NULL == fp)
	{
		perror("fopen error");
		return -1;
	}
	fclose(fp);
	return 0;
}
fgetc & fputc
  • 声明
int fputc(int ch, FILE * stream);


int fgetc(FILE * stream);
//返回值:成功返回读到的字符的ASCII码;失败返回 -1
  • 示例
#define _CRT_SECURE_NO_WARNINGS
#include
#include

int main(void)
{
	FILE* fp = fopen("test.txt", "w");
	if (NULL == fp)
	{
		perror("fopen error");
		return -1;
	}
	int ret = fputc('A', fp);
	printf("ret = %d", ret); //65
	fclose(fp);
	return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include
#include

int main(void)
{
	FILE* fp = fopen("test.txt", "r");
	if (NULL == fp)
	{
		perror("fopen error");
		return -1;
	}
	while (1)
	{
		int ch = fgetc(fp);
		if (EOF == ch) //文件读取结束标记为 EOF,值为 - 1
		{
			printf("end ------- ");
			break;
		}
		printf("ch = %cn", ch);
	}
	fclose(fp);
	return 0;
}
fgets & fputs
  • 声明
int fputs(const char* str, FILE * stream);


char * fgets(char* s, int size, FILE * stream);

  • 示例
int main(void)
{
	char buf[10] = { 0 };
	fgets(buf, 10, stdin);
	printf("buf: %s", buf);
    FILE* fp = fopen("test.txt", "w");
	fputs(buf, fp);
	fclose(fp);
	return 0;
}
fprintf & fscanf
  • 声明
int fprintf(FILE * stream, const char * format, ...);


int fscanf(FILE* stream, const char * format, ...);

  • 示例
fprintf(fp, "%d = %d %c %dn", 15, 3, '*', 5);
int main(void)
{
	FILE* fp = fopen("test.txt", "r");
	int sum, a, b;
	char operate;
	int count = fscanf(fp, "%d=%d%c%d", &sum, &a, &operate, &b);
	fclose(fp);
	printf("count=%dn", count); //count=4
	printf("%d=%d%c%d", sum, a, operate, b); //10=1+9
	return 0;
}
fread & fwrite
  • 声明

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE * stream);


size_t fread(void * ptr, size_t size, size_t nmemb, FILE * stream);

  • 示例
#define _CRT_SECURE_NO_WARNINGS
#include
#include

typedef struct Demo
{
	int age;
	char name[10];
	int number;
} Demo;

int main(void)
{
	printf("sizeof(Demo) = %lun", sizeof(Demo)); //20
	Demo arr[3] = {
		{18, "zhangsan", 9},
		{21, "lisi", 12},
		{20, "wangwu", 10}
	};
	FILE* fp = fopen("test.txt", "w");
	if (!fp)
	{
		perror("fopen error");
		return -1;
	}
	int bytes = fwrite(&arr[0], 1, sizeof(Demo) * 3, fp);
	printf("bytes=%dn", bytes); //60
	fclose(fp);
	return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include
#include

typedef struct Demo
{
	int age;
	char name[10];
	int number;
} Demo;

int main(void)
{

	FILE* fp = fopen("test.txt", "r");
	if (!fp)
	{
		perror("fopen error");
		return -1;
	}
	Demo arr[3];
	Demo* p = arr;
	while (1)
	{
		int ret = fread(p++, 1, sizeof(Demo), fp);
		printf("ret=%dn", ret);
		if (ret == 0) // 或 feof(fp)
		{
			break;
		}
	}
	fclose(fp);
	Demo temp;
	for (size_t i = 0; i < sizeof(arr)/sizeof(arr[0]); i++)
	{
		temp = *(arr + i);
		printf("%d: name=%s, age=%d, num=%dn", i, temp.name, temp.age, temp.number);
	}
	return 0;
	
}

  • 文件复制
#define _CRT_SECURE_NO_WARNINGS
#include

void mycopy()
{
	FILE* rfile = fopen("C:\Users\ysw15\Pictures\Saved Pictures\帅.jpg", "rb");
	FILE* wfile = fopen("C:\Users\ysw15\Pictures\Saved Pictures\帅2.jpg", "wb");

	int ret;
	char buf[1024] = { 0 }; //缓冲区,设置每次读取的大小
	while (1)
	{
		ret = fread(buf, 1, sizeof(buf), rfile);
		if (0 == ret)
		{
			break;
		}
		fwrite(buf, 1, ret, wfile);
	}

	fclose(rfile);
	fclose(wfile);
}

int main(void)
{
	mycopy();
	return 0;
}
remove & rename
  • 声明
int remove(const char * pathname);


int rename(const char* oldpath, const char* newpath);

fseek & ftell & rewind
  • 声明

int fseek(FILE * stream, long, offset, int whence);


long ftell(FILE * stream);


void rewind(FILE * stream);


  • 示例
#define _CRT_SECURE_NO_WARNINGS
#include

typedef struct Student
{
	int age;
	char name[10];
	int number;
} Student;

int main(void)
{
	Student stus[4] =
	{
		{18, "zhangsan", 201},
		{19, "lisi", 302},
		{20, "wangwu", 109},
		{21, "zhaoliu", 209}
	};

	FILE * fp = fopen("C:\Users\ysw15\Pictures\Saved Pictures\test", "wb+");

	//写入文件
	fwrite(&stus[0], 1, sizeof(stus), fp);

	//读取wangwu数据
	fseek(fp, sizeof(Student) * 2, SEEK_SET);
	Student wangwu;
	fread(&wangwu, 1, sizeof(Student), fp);
	printf("1. name=%s, age=%d, number=%dn", wangwu.name, wangwu.age, wangwu.number); //1. name=wangwu, age=20, number=109

	//获取当前读写指针偏移量
	int offset = ftell(fp);
	printf("当前偏移量为:%dn", offset); //60

	//回到文件起始位置
	rewind(fp);

	//读取zhangsan数据
	Student zhangsan;
	fread(&zhangsan, 1, sizeof(Student), fp);
	printf("2. name=%s, age=%d, number=%dn", zhangsan.name, zhangsan.age, zhangsan.number); //2. name=zhangsan, age=18, number=201

	//获取文件大小
	fseek(fp, 0, SEEK_END); //将读写指针放到未见末尾
	int size = ftell(fp); //读取文件偏移量大小
	printf("文件的大小为:%dn", size); //80

	fclose(fp);
	return 0;
}
stat
  • 声明
#include
#include

int stat(const char* path, struct stat* buf);


struct stat 
{
    def_t st_dev; //文件的设备编号
    ino_t st_ino; //节点
    mode_t st_mode; //文件的类型和存取的权限
    nlink_t st_nlink; //连到该文件的硬连接数据,刚建立的文件该值为1
    uid_t st_uid; //所属用户ID
    gid_t st_gid; //所属组ID
    dev_t st_rdev; //设备类型,如果该文件尾设备文件,则此值为其设备编码
    off_t st_size; //文件字节数(文件大小)
    unsigned long st_blksize; //块大小(文件系统的I/O缓冲区大小)
    unsigned long st_blocks; 块数
    time_t st_atime; //最后一次访问时间
    time_t st_mtime; //最后一次修改时间
    time_t st_ctime; //最后一次(属性)变更时间
}
  • 示例
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include

int main(void)
{
    struct stat buf;
    int ret = stat("test.txt", & buf);
    printf("file size is %dn", buf.st_size);
    return 0;
}

fflush
  • 声明
int fflush(FILE * stream);

feof
  • 声明
int feof(FILE * stream);

  • 示例
if (feof(fp))
{
    fgetc(fp);
    break;
}
WIndows和Linux的先读后写差异
  • 说明

对于先读后写的情况,在进行写入时,Windows下返回写入成功,但是实质上并未真正写入,此时需要添加额外的语句fseek(fp, 0, SEEK_CUR);

  • 示例
#define _CRT_SECURE_NO_WARNINGS
#include

int main(void)
{
	
	FILE* fp = fopen("C:\Users\ysw15\Pictures\Saved Pictures\test.txt", "rb+");

	char buf[6] = { 0 };
	char* ptr = fgets(buf, 6, fp);
	printf("buf=%s, ptr=%sn", buf, ptr); //buf=11111, ptr=11111

	
	fseek(fp, 0, SEEK_CUR); //Windows额外语句
	int ret = fputs("AAAAA", fp);
	printf("ret = %d", ret); //0

	fclose(fp);
	return 0;
}

几个函数与关键字 putchar
  • 说明

输出一个字符到屏幕上

  • 示例
putchar(97);
putchar('a');
puts
  • 说明

将一个字符串(只能是字符串)输出到屏幕上,自带换行符

scanf
  • 说明

接收键盘输入的数据

  • scanf_s

建议使用 scanf_s,因为scanf在读取时不会检查边界,存在造成内存访问越界的问题,如分配了5字节的空间,但是读入了10字节,多余的部分会被写入到别的空间上去,

//注意,在ANSI C中没有scanf_s,只有scanf
char buf[6] = {''};
//scanf("%s", buf); //如果输入1234567890,printf一样会输出显示,但是后面的67890会被写到别的非数组空间中,而一旦这部分空间恰巧被系统分配给其他地方使用,可能就会将其进行覆盖,即不再是67890,即此时printf输出的将可能不是67890
scanf_s("%s", buf, 6) //最后一个字节用于存放''
  • 示例
#include 

int main(void)
{
	
	char c1, c2, c3;
	scanf_s("%c%c%c", &c1, 1, &c2, 1, &c3, 1); //scanf("%c%c%c", &c1,&c2, &c3);
	printf("c1 = %cn", c1);
	printf("c2 = %cn", c2);
	printf("c3 = %cn", c3);
	
	int a1, a2, a3;
	scanf_s("%d %d %d", &a1, 1, &a2, 1, &a3,1); //需要用空格隔开,否则会有歧义,因为123可以当做一个是一个整数
	printf("a1 = %dn", a1);
	printf("a2 = %dn", a2);
	printf("a3 = %dn", a3);
	
    char str[6];
    scanf_s("%s", str, 6); //数组名本身就是地址,所以这里无需取地址符
    printf("str = %s", str);
	return 0;
}
  • 空格问题

在接收字符串时,无论是scanf还是scanf_s,除了敲击换行, 输入空格也会终止接收输入

  • 使用正则表达式接收空格
int main(void) {
	char str[11] = { 0 };
	scanf("%[^n]", str); //表示将输入的数据,除了n之外的都放入到字符数组str的内存地址中
	printf("res: %s", str);
	return 0;
}
goto
  • goto的标签可以在goto之后定义
#include 

int main(void)
{
	printf("1n");
	goto tag;
	printf("2n");
tag:
	printf("3n");
}
  • goto仅在当前函数中有效(可以在一个函数的两个for之间跳转)
#include 

int main(void)
{
	int j = 1; //因为直接进入for,所以需要在for的外边进行初始化,否则打印的将都是自动生成的一个随机数(在goto之前初始化)
	for (int i = 1; i <= 5; i++) {
		if (i == 3) {
			goto tag;
		}
		printf("i = %dn", i);
	}
	for (; j <= 5; j++) {
		tag:
		printf("j = %dn", j);
	}
	
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/289662.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号