直指重要知识点的C语言笔记,这一篇足矣!

直指重要知识点的C语言笔记,这一篇足矣!_第1张图片
一直零零散散记录C语言知识点,为了之后更好查找将所学习的知识点进行了一个整合,方便自己和学回顾C语言的朋友,如有纰漏,欢迎指出。

文章目录

  • 第壹部分:C语言的基础知识
    • 一:C语言程序设计
    • 二:数据类型
      • 1. 数据大小及范围
      • 2. 类型转换
      • 3. 整形截断和提升
    • 三:转义字符
  • 第贰部分:操作符 关键字 表达式
    • 一:操作符
    • 二:关键字
      • 1. 关键字typedef
      • 2. 关键字static
      • 3. 宏替换
    • 三:表达式
      • 1. 逗号表达
      • 2. break和continue
      • 3. 二分查找函数
  • 第叁部分:函数 数组
    • 一:函数
      • 1. 函数的参数和调用
      • 2. 函数的嵌套调用和链式访问
      • 3. 函数的声明和定义
      • 4. 函数的递归(小难点)
    • 二:数组
      • 1. 数组作为函数参数
      • 2. 柔性数组的使用
  • 第肆部分:结构体 枚举 联合
    • 一:结构体
      • 1. 结构体的声明
      • 2. 结构体成员的访问和传参
      • 3. 结构体的内存对齐
    • 二:枚举
    • 三:联合(共用体)
    • 四:位段
  • 第伍部分:字符串,动态内存管理,文件
    • 一:字符串函数
      • 1. 字符串函数的实现
    • 二:内部存储结构
      • 0. 内存和外存的区别
      • 1. 原码反码补码
      • 2. 大小端
    • 三:动态内存管理
      • 1. 内存区域划分
      • 2. 动态内存管理
        • 1.malloc
        • 2 calloc
        • 3. recallo
        • 4. 稳定性测试,内存泄漏
    • 四:文件操作
      • 1. 文件
      • 2. 文件的读写
      • 3. 文件编译链接
  • 第陆部分:指针
    • 一:指针类型和数组名
    • 二:指针运算
    • 三:指针和数组的关系
    • 四:二级指针和指针数组
      • 1. 二级指针
      • 2. 指针数组和数组指针
      • 3. 函数指针的调用和定义

第壹部分:C语言的基础知识

一:C语言程序设计

  1. 头文件的介绍
    #include < >会在系统目录中查找头文件,而#include “ ”先在当前项目目录之中查找,然后再去系统目录中查找,这样的做法也是防止头文件被重复包含。所以一般来说,如果使用的包含标准库头文件,就使用 <>,包含着自己的头文件的话就使用 “ ” 。
  2. 冯诺依曼体系
    直指重要知识点的C语言笔记,这一篇足矣!_第2张图片

二:数据类型

1. 数据大小及范围

不同的系统所对应的数据类型大小是不同的:
直指重要知识点的C语言笔记,这一篇足矣!_第3张图片
主要数据的范围

    类型       无符号(unsigned)           有符号(signedchar          0~+255                    -128~+127
    
    short        0~65535                  -32768~32768
    
    int        0~4294967295            -2147483648~+2147483648(21亿)
    

例如:char的取值为0-255 则如果是它的数组,所能够容纳的值就只有256个
直指重要知识点的C语言笔记,这一篇足矣!_第4张图片

2. 类型转换

  1. 两种不同的类型转换

操作数类型不同,且不属于基本数据类型时,需要将操作数转化为所需类型,这个过程为强制类型转换,强制类型转换具有两种形式:显式强制转换和隐式强制类型转换。

显式强制类型转换:通过一定语句和相关的前缀,将所给出的类型强制转换成为我们所需要的类型。

int n=0xab65char a=char)n;

强制类型转换的结果是将整型值0xab65的高端一个字节删掉,将低端一个字节的内容作为char型数值赋值给变量a,经过类型转换后n值并未改变。
隐式强制类型转换:当算术运算符两侧的操作数类型不匹配的时候,会触发“隐式的转换”,先转换成相同的类型,之后再进行计算。
C语言在以下四种情况下会进行隐式转换:
1、算术运算式中,低类型能转为高类型。
2、赋值表达式中,右边表达式的值自动隐式转换为左边变量的类型,并赋值给他。
3、函数调用中参数传递时,系统隐式地将实参转换为形参的类型后,赋给形参。
4、函数有返回值时,系统将隐式地将返回表达式类型转换为返回值类型,赋值给调用函数。

  1. 自动类型转换

在C语言中,自动类型转换遵循以下规则:
1、按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。
a、若两种类型的字节数不同,转换成字节数高的类型
b、若两种类型的字节数相同,且一种有符号,一种无符号,则转换成无符号类型
2、所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。
4、char型和short型(在visual c++等环境下)参与运算时,必须先转换成int型。
5、在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度,丢失的部分直接舍去。
一些需要注意的东西:
直指重要知识点的C语言笔记,这一篇足矣!_第5张图片
类型转换阶层图
小技巧:想要实现运算之后的四舍五入计算,只需要给一个数加上0.5就可以直接完成。

例题1. unsigned int

其中unsigned int 不能表示小于0的,所以是恒大于0,则是永远成立的
unsigned int i;
	for (i = 9; i >= 0; i--) {
		printf("%u\n", i);// %u无符号十进制表示
	}
	因此此程序是一个无限循环的程序

例题2 .打印一个无符号的整型

char a=-128;
printf("%u",a);

直指重要知识点的C语言笔记,这一篇足矣!_第6张图片
例题3. 无符号0减去有符号-1

unsigned int i;
i-1 => i+(-1) 

直指重要知识点的C语言笔记,这一篇足矣!_第7张图片

3. 整形截断和提升

  1. 整型截断

把四个字节的变量赋值给一个字节的变量,会存在截断,则对应的截取它低位上的值:

四字节变量
int 0x11111111 11111111 11111111 11111111;
截断后一字节变量
char 0x1111 1111;
  1. 整形的提升和意义

整型提升的意义
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU是难以直接实现两个8比特字节的直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
如:

char a,b,c;
a = b + c;
b和c的值被提升为普通整型,然后再执行加法运算。
加法运算完成之后,结果将被截断,然后再存储于a中
  1. 如何进行整型提升

整形提升是按照变量的数据类型的符号位来提升的:

/负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111

/正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001

/无符号整形提升,高位直接补0
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}

c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节.
表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof( c ) ,就是1个字节.

三:转义字符

转义字符 释义
\? 在书中写连续多个问号时使用,防止他们被解析成为三个字母词
\’ 用于表示字符串常量 ‘
\’’ 用于表示一个字符串内部的双引号
\\ 用于表示一个反斜杠,防止它被解释为一个转义序列符
\a 警告字符,蜂鸣
\b 退格符
\f 进纸符
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ddd ddd表示1~3个八进制的数字。 如: \130
\xddd ddd表示3个十六进制数字。 如: \x030

第贰部分:操作符 关键字 表达式

一:操作符

<< 左移操作符
>> 右移操作符

左移操作符移位规则:左边抛弃,右边补0
右移操作符移位规则:
首先右移运算分两种: -1
1. 逻辑移位 左边用0填充,右边丢弃
2. 算术移位 左边用原该值的符号位填充,右边丢弃
逻辑右移:左边补0;
逻辑右移
算术右移:左边用原该值的符号位进行填充,由于是负数,所以符号位为1,即左边补1
算术右移
注意:整数/0 所得到的结果显示为“运行时出错”

二:关键字

1. 关键字typedef

typedef 顾名思义是类型定义,这里应该理解为类型重命名。

//将unsigned int 重命名为uint_32, 所以uint_32也是一个类型名
typedef unsigned int uint_32;
int main()
{
//观察num1和num2,这两个变量的类型是一样的
unsigned int num1 = 0;
uint_32 num2 = 0;
return 0;
}

2. 关键字static

在C语言中:static是用来修饰变量和函数的
结论:static修饰局部变量改变了变量的生命周期,让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束。
结论:一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用。
结论:一个函数被static修饰,使得这个函数只能在本源文件内使用,不能在其他源文件内使用。

3. 宏替换

#define DEBUG_PRINT printf("file:%s line:%d", __FILE__, __LINE__)
#define For for(;;)

//1. 系统已经定义好的
	printf("file:%s line:%d", __FILE__, __LINE__);
	DEBUG_PRINT;

宏和函数的对比
直指重要知识点的C语言笔记,这一篇足矣!_第8张图片
在这里插入图片描述

宏续接
1.

#define PRINT(FORMAT,VALUE) \
		printf("the value of"#VALUE"is "FORMAT"\n",VALUE);
宏只会替换一行之中的后面部分,如果要写成多行,必须使用续行符 “\”
其中#则会把后面的字符换成了字符串
  #define ADD_TO_SUM(num,value) \
		sum##num += (value*3.14+10.1456);
其中##是把两边的符号合并成一个符号 
double sum1 = 1, sum2 = 2,sum3 = 3;
	ADD_TO_SUM(1, 10);
	ADD_TO_SUM(2, 10);
	ADD_TO_SUM(3, 10); // 则成为sum3+=(10*3.14+10.1456)

三:表达式

1. 逗号表达

exp1, exp2, exp3, …expN

逗号表达式,就是用逗号隔开的多个表达式。 逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。

2. break和continue

  1. while中的break是用于永久终止循环的。
  2. continue是用于终止本次循环的,也就是本次循环中continue后边的代码不会再执行,而是直接跳转到while语句的判断部分,进行下一次循环的入口判断。
  3. While中的continue接下来所执行的是判定循环的条件
  4. for中的continue接下来所执行的是表达式3,然后再是表达式2;

3. 二分查找函数

int bin_search(int arr[], int left, int right, int key)
{
	int mid = 0;
	while(left<=right)
	{
		mid = (left+right)>>1;
		if(arr[mid]>key)
		{
			right = mid-1;
		}
		else if(arr[mid] < key)
		{
			left = mid+1;
		}
		else
			return mid;//找到了,返回下标
		}
	return -1;//找不到

第叁部分:函数 数组

一:函数

1. 函数的参数和调用

函数的参数分为实参和形参。

  1. 实际参数(实参)

真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。

  1. 形式参数(形参)
    形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。

  2. 形参和实参的比较
    直指重要知识点的C语言笔记,这一篇足矣!_第9张图片
    通过函数的调用,函数之中拥有了和实参一模一样的内容,所以我们可以简单的认为:实参实例化之后其实就相当于实参的一份临时拷贝。

2. 函数的嵌套调用和链式访问

  1. 函数的调用:
    传值调用
    函数的形参和实参分别占有不同的内存块,对形参的修改不会影响实参。
    传址调用
  2. 传址调用是把函数外部创建的变量的内存地址传递给函数参数的一种调用函数的方式。
  3. 这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

3. 函数的声明和定义

函数的声明

  1. 告诉编译器有一个函数叫什么,所使用的参数是什么,返回类型是什么,但是具体是不是存在,无关紧要。
  2. 函数的声明一般出现在函数的使用之前,要满足先声明后使用。
  3. 相关函数的声明一般要放在头文件中。
    函数的定义:
  4. 是指函数的具体实现,交代函数的功能实现的。

4. 函数的递归(小难点)

  1. 什么是递归?
    程序调用自身的编程技巧称之为递归
    递归思想:大事化小,小事化了!
  2. 递归的两个必要条件
  • 存在限制条件,当满足这个限制条件的时候,递归便不再继续!
  • 每次递归调用之后越来越接近这个限制条件,这是它的收敛性。
  • 写递归程序的话,往往就是对正在进行的问题进行不断地细化和拆分。
  1. 递归所产生的栈溢出
    系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者说是死递归的话,有可能导致一直在不断的开辟栈空间,最终导致栈空间耗尽,我们将这样的现象称为栈溢出。

  2. 如何解决递归溢出?

  • 将递归改写成非递归
  • 使用static对象替代nonstatic局部对象,在递归函数的设计中,可以使用static对象替代nonstatic局部对象(即所谓的栈对象),这样不仅可以减少每次递归调用和返回时所产生和释放的nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。

二:数组

数组是以组相同类型元素所构成的集合。

  1. 数组是使用下标来访问的,下标是从0开始。
  2. 数组的大小可以通过计算得到。
  3. sizeof 计算的是数组的总大小,是多少就是多少。
  4. 计算字符数组长度的话,计算\0
  5. sizeof 是一个运算符,具有一个重要的特性。
  6. 数组在内存中是连续存放的。

在这里插入图片描述

1. 数组作为函数参数

往往我们在写代码的时候,会将数组作为参数传个函数

#include 
int main()
{
	int arr[] = {3,1,7,5,8,9,0,2,4,6};
	bubble_sort(arr, sz); //使用的时候,传数组元素个数
	void bubble_sort(int arr[], int sz)//参数接收数组元素个数
	{
	
	}

数组作为函数参数的时候,是不会把整个数组传递过去的,实际上只是把数组的首元素的地址传递过去了,所以即使在函数参数部分写成数组的形式:int arr[ ]表示,依然会隐式转为一个指针:int * arr

2. 柔性数组的使用

柔性数组:结构体的最后可以定义一个大小是未知的这样一个数组。

typedef struct st_type
{
	int i;
	int a[];//柔性数组成员
}type_a;

如果使用sizeof(结构体)的话,其中大小的计算不包含柔性数组。
直指重要知识点的C语言笔记,这一篇足矣!_第10张图片

第肆部分:结构体 枚举 联合

一:结构体

1. 结构体的声明

结构是一些值的集合,这些值被我们称为成员变量,结构的每个成员可以是不同类型得变量。
例如我们想要去描述一个学生得具体信息:

typedef struct Stu
{
	char name[20];名字
	int age;年龄
	char sex[5];性别
	char id[20];学号
}Stu;分号不能丢
  1. 结构体定义:结构体类型定义
typedef struct Student
{
	char name[128];
	int age;
	char tel[20];
}S; //S是结构体类型

struct Point
{
	int x;
	int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
  1. 结构体的初始化:定义变量的同时赋初值
struct Point p3 = {x, y};
struct Stu //类型声明
{
	char name[15];//名字
	int age; //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
一个结构体不能够包含自己类型的结构体变量,但可以包含自己类型的结构体指针。

2. 结构体成员的访问和传参

struct S s;
strcpy(s.name, "zhangsan");//使用.访问name成员
s.age = 20;//使用.访问age成员

结构体的指针,该如何访问成员:

void print(struct Stu* ps)
{
	printf("name = %s age = %d\n", (*ps).name, (*ps).age);
	//使用结构体指针访问指向对象的成员
	printf("name = %s age = %d\n", ps->name, ps->age);
}

小技巧: 结构体传参的时候,所传的一定要是结构体的地址,或者说是尽量去传指针!

3. 结构体的内存对齐

  1. 对其规则的计算
  • 第一个成员在与结构体变量偏移量为0的地址处;
  • 其他成员变量要对其到某个数字(对齐数)的整数倍的地址处;
  • 对齐数= 编译器默认的一个对齐数与该成员大小的较小值 (vs中默认为8). 如果想要改变默认对齐数:#pragma pack(1)则表示没有对齐数 括号里的数字是多少对齐数就是多少。
  • 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含潜逃结构体的对齐数)整数倍。
  1. 为什么要有内存对齐
    直指重要知识点的C语言笔记,这一篇足矣!_第11张图片 让占用空间小的成员尽量集中在一起, 以空间来换取时间!

二:枚举

枚举:一个定义常量的集合;
默认里面所列举出来的第一个值是零,如果第一个所举出来的值给5,则表示从5往后走;
直指重要知识点的C语言笔记,这一篇足矣!_第12张图片

  1. enumint是否应该划上等号呢?

在C语言之中,enumint是等价的,搞个枚举的目的只是为了代码的可读性能够更好一点;而enum所表示出来的含义,则就是我们所说到的枚举出来的概念,整个概念是不应该和整数进行划等号的;而枚举在内存之中的存储和int一样的,都是以字节序+原码反码补码的形式来进行存储。

三:联合(共用体)

一种特殊的自动类型,里面的这些成员,共用一块空间,谁大就用谁的空间,当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。(最为特殊的例子就是对于ip的使用)
直指重要知识点的C语言笔记,这一篇足矣!_第13张图片

四:位段

位段的内存布局是和平台强相关的,并没有统一的标准。位段更多的在网络通信的时候会进行应用,

struct A {
	int _a:2;// _a这个变量只占2个bit位
	int _b:5;
	int _c:10;
	int _d:30;
  1. 位段的成员必须是int ,unsigned int,或者signed int
  2. 位段的成员后边有一个冒号和一个数字,代表整个成员占据几个bit位。
    直指重要知识点的C语言笔记,这一篇足矣!_第14张图片

第伍部分:字符串,动态内存管理,文件

一:字符串函数

  1. 字符串的结束标志是一个 \0 的转义字符。在计算字符串长度strlen()的时候 \0 是结束标志,不算作字符串内容。一个字符数组如果没有\0 就不是字符串,就不能够使用strlen。
  2. strlen 字符串是以’\0’来作为结束标志的 strlen函数返回的是斜杠0前面的出现的字符个数,不包括\0 (sizeof是一个运算符)。

1. 字符串函数的实现

  1. strlen计算长度函数

strlen的使用

	char *p1 = "hello world";
	printf("%d\n", strlen(p1));
	char p2[] = "hello world"; //char p2[] = "hello\\0world";  则长度为12 大小为13
	//p2[5] = '0'; 结果还为11
	//p2[5] = '\0';结果为5 
	printf("%d\n", strlen(p2));//结果也为11 但此数组的大小是12
	printf("%d\n", sizeof(p2));

strlen的代码实现
直指重要知识点的C语言笔记,这一篇足矣!_第15张图片

  1. strcpy拷贝函数

拷贝函数的实现

char* my_strcpy(char*dst, const char* src)
{
	assert(dst != NULL);
	assert(src != NULL);
	char* ret = dst;
	while (*src != '\0')
	{
		*dst = *src;
		++src;
		++dst;
	}
	*dst = '\0';
	return ret;
}

  1. strcat追接/拼接函数
#include 
#include 
#include 
// strcat  zhuijia pinjie 
char* my_strcat(char* dst, const char* src){
	assert(dst&&dst);
	char *ret = dst;
	while (*dst != '\0')
	{
		++dst;
	}
	while (*dst++ = *src++);//运算符
	//while (*src != '\0'){
	//	*dst = *src;
	//	++dst;
	//	++src;
	//}  
	//*dst = *src;
	return ret;
}
int main(){
	char *p1 = "hello";
	char p2[11] = "world";

	//strcpy(p2,p1);
	strcat(p2, p1);
	return 0;
}
  1. strcmp比较函数

Strcmp 所比较字符串每个字符的ASCII码
代码的实现

int my_strcmp(const char* str1, const char* str2){
	assert(str1&&str2);
	unsigned char* s1 = (unsigned char*)str1;
	unsigned char* s2 = (unsigned char*)str2;
	while (*s1&&*s2)
	{
		if (*s1 > *s2)
		{
			return 1;
		}
		else if (*s1 < *s2)
		{
			return -1;
		}
		else {
			return 0;
		}
	}
	if (*s1 == '\0'&&*s2 == '\0')
	{
		return 0;
	}
	else if(*s1=='\0')
	{
		return -1;
	}
	else
	{
		return 1;
	}
	return 0;
}
int main(){
	char *p1 = "hello";
	char *p2 = "world";
	printf("%d\n", my_strcmp(p1, p2));// 大于1 小于-1 等于0
	return 0;
}
  1. strncpy strncat strncmp 进行具体数字的操作
#include 
#include 
#include 

int main(){
	char *p1 = "hellohello";
	char p2[100] = "world";
	strncpy(p2, p1, 5);
	printf("%s\n", p2);

	strncat(p2, p1, 5);
	strncmp(p2, p1, 5);
	return 0;
}
  1. strstr比较一个字符串中是否存在另一个字符串函数
int main(){
	char *p1 = "abcde";
	char *p2 = "deabc";
	char p3[11];
	strcpy(p3, p1);
	strcat(p3, p1);
	if (strstr(p3, p2) != NULL)
	{
		printf("是旋转字符串\n");
	}
	else
	{
		printf("不是旋转字符串\n");
	}
	return 0;
}  

实现函数

const char* my_strstr(const char* src, const char* sub){
	assert(src&&sub);

	const char* srci = src;
	const char* subi = sub;
	while (*srci!='\0')
	{
		while (*srci==*subi&&*subi!='\0')
		{
			++srci;
			++subi;
		}
		if (*subi=='\0')
		{
			return src;
		}
		else
		{
			subi = sub;
			++src;
			srci = src;
		}
	}
	return NULL;
}
  1. strtok分割函数
    直指重要知识点的C语言笔记,这一篇足矣!_第16张图片
    直指重要知识点的C语言笔记,这一篇足矣!_第17张图片
  2. memcpy函数:拷贝内存
#include 
#include 
#include 
#include 
// memcpy 所有的拷贝  

void *my_memcpy(void*dst, const void *src, size_t num)
{
	assert(dst&&src);
	char * str_dst = (char*)dst;
	char * str_src = (char*)src;
	for (size_t i = 0; i < num; i++)
	{
		str_dst[i] = str_src[i];
	}
	return dst;
}

  1. memmove函数,移动解决内存重叠的问题
    直指重要知识点的C语言笔记,这一篇足矣!_第18张图片
void *my_memmove(void*dst, const void *src, size_t num)
{
	assert(src&&dst);
	char * str_dst = (char*)dst;
	char * str_src = (char*)src;
	if (str_src < str_dst&&str_dst<str_src+num)//后重叠,或者不重叠,从后往前拷
	{
		for (int i = num - 1; i >= 0; --i)
		{
			str_dst[i] = str_src[i];
		}
	}
	else// 前重叠 不重叠 从前往后拷
	{
		for (size_t i = 0; i < num; ++i)
		{
			str_dst[i] = str_src[i];
		}
	}
	return dst;
}

直指重要知识点的C语言笔记,这一篇足矣!_第19张图片

二:内部存储结构

0. 内存和外存的区别

  1. 内存的访问速度快,外存的访问速度慢
  2. 内存存储空间比较小,外存存储空间比较大
  3. 内存成本比较高,外存成本更低
  4. 内存掉电之后数据丢失,外存掉电之后数据仍然还在

1. 原码反码补码

正数:原码是其本身 反码 补码和原码是完全相同的
  7  原码 0000 0111
     反码 0000 0111
     补码 0000 0111
 
负数:原码是其本身 反码符号位不变,其余位全部取反,补码则是在反码的基础上加1
 -7   原码 1000 0111
      反码 1111 1000
      补码 1111 1001
零:零分为正零+0 和负零 -0
  +0  原码 0000 0000
      反码 0000 0000
      补码 0000 0000
  -0  原码 1000 0000
      反码 1111 1111
      补码 0000 0000
补码往回转的话,依然是取反+1 切记!!!

2. 大小端

大端是和我们的认知是相符合的,则是从低到高开始进行排列。
小端的话则和我们的认知相反,它是从小往大的排列。

一个代码检测自己的电脑到底是大端序还是小端序

#include 

int main(){
    int x=1;
    char c=(char)x;
    if(c==1)
    {
    	printf("小端机\n");
    }
    else if(c==0)
    {
    	printf("大端机\n");
    }
    return 0;
}

ASCII 是我们所看见的字母和数字在内存之中所在的十进制地址值

三:动态内存管理

1. 内存区域划分

直指重要知识点的C语言笔记,这一篇足矣!_第20张图片
直指重要知识点的C语言笔记,这一篇足矣!_第21张图片

2. 动态内存管理

直指重要知识点的C语言笔记,这一篇足矣!_第22张图片

1.malloc

Int* a=int(*)malloc(sizeof(int)*100); 申请100个空间不想用的话就还他自由 free(a);

直指重要知识点的C语言笔记,这一篇足矣!_第23张图片
直指重要知识点的C语言笔记,这一篇足矣!_第24张图片
直指重要知识点的C语言笔记,这一篇足矣!_第25张图片

2 calloc

calloc void* calloc(size_t num, size_t size);把num个大小为size的元素开辟一个空间,值全部为0

3. recallo

void* realloc(void* ptr,size_t size); 把一个曾经已经申请到的内存进行扩容

Realloc 扩容 重新开个大的,将原来的内容拷贝过去,之后将原来的空间释放(后面如果有足够地空间,直接扩容,如果没有就找足够地空间)
在这里插入图片描述
直指重要知识点的C语言笔记,这一篇足矣!_第26张图片

4. 稳定性测试,内存泄漏

直指重要知识点的C语言笔记,这一篇足矣!_第27张图片

  • 非动态内存不能释放,一块内存不能释放多次
  • 程序结束之后申请的内存会自动泄露!
  • 内存泄漏是指针丢了,内存是不会丢的
  • 长期的内存泄漏 会导致运行越来越慢 最终挂掉!
    直指重要知识点的C语言笔记,这一篇足矣!_第28张图片

四:文件操作

1. 文件

直指重要知识点的C语言笔记,这一篇足矣!_第29张图片直指重要知识点的C语言笔记,这一篇足矣!_第30张图片

    //2进制
fwrite(&a, sizeof(int), 1, fin);
    //文本
fputs("10000", fin);

直指重要知识点的C语言笔记,这一篇足矣!_第31张图片
直指重要知识点的C语言笔记,这一篇足矣!_第32张图片
直指重要知识点的C语言笔记,这一篇足矣!_第33张图片
直指重要知识点的C语言笔记,这一篇足矣!_第34张图片

2. 文件的读写

直指重要知识点的C语言笔记,这一篇足矣!_第35张图片
直指重要知识点的C语言笔记,这一篇足矣!_第36张图片
文件的随机读写代码

FILE* fout = fopen("learn3_4_1.c", "r");
    
    // 读
    char ch = fgetc(fout);
    while (ch != EOF)
    {
        printf("%c", ch);
        ch = fgetc(fout);
    }

    //清空写
    FILE* fin = fopen("learn3_4_1.c", "w");
    fputc('h', fin);
    fputc('e', fin);
    fputc('l', fin);
    fputc('l', fin);
    fputc('o', fin);

    //追加写
    FILE* fain = fopen("learn3_4_1.c", "a");
    fputs("#include ", fain);
    fputs("int main{", fain);
    fputs("return 0;", fain);
    fputs("}", fain);

    //定位
    FILE* fdin = fopen("file.txt", "r+");
    char ch = fgetc(fdin);
    while (ch != EOF)
    {
        if (ch == '/')
        {
            char next = fgetc(fdin);
            if (next == '/')
            {
                fputc('*',fdin);
            }
        }
        ch = fgetc(fdin);
    }
fclose(fdin);
}

在这里插入图片描述
直指重要知识点的C语言笔记,这一篇足矣!_第37张图片
直指重要知识点的C语言笔记,这一篇足矣!_第38张图片
fseek:文件的随机读写

int main() {
    FILE* fout = fopen("test.txt", "r");
    fseek(fout, 5, SEEK_SET);//设置的文件的开始
    //fseek(fout, 0, SEEK_END);设置的文件的末尾
    //fseek(fout, 5, SEEK_CUR);//跳过几个位置
    //fseek(fout, 5, SEEK_CUR);//跳过几个位置

    //1.读前五个后跳过五个
    //char out_ch = fget(fout);
    //for(int i=0;i<5;i++){
    //}
    // 之后再跳5个

    //2. 算文件的大小
    //fseek(fout, 0, SEEK_END);设置的文件的末尾
    //printf("文件的大小:%d", ftell(fout));// 计算文件大小

    //3.判断文件结束 如果没读到结尾会返回非零
    char ch = fget(fout);
    while (ch != EOF) {
        printf("%c", ch);
        ch = fgetc(fout);
    }
    if (feof(fout) != 0) {
        printf("read end of file\n");
    }
    /*if (ferror(fout) != 0) {
        printf("read error");
    }*/

    //4. 读视频
    fseek(fout, 0, SEEK_END); //设置的文件的末尾
        long long n = ftell(fout);
        printf("%d\n",n);
        fseek(fout, 0, SEEK_SET); //设置的文件的kaishi
        while (n--) {
            printf("%c", ch);
            ch = fgetc(fout);
        }

    fclose(fout);
    system("pause");
    return 0;
}

3. 文件编译链接

  1. 文件编译过程
    直指重要知识点的C语言笔记,这一篇足矣!_第39张图片
    直指重要知识点的C语言笔记,这一篇足矣!_第40张图片
    直指重要知识点的C语言笔记,这一篇足矣!_第41张图片
    编译阶段进行的内容: const ,constexpr, template, type_tarits ,sizeof, #define.

  2. 条件编译
    直指重要知识点的C语言笔记,这一篇足矣!_第42张图片

#ifdef __DEBUG__//有这样的就编译,没有就不编译
		printf("%d\n", arr[i]);
#endif//

直指重要知识点的C语言笔记,这一篇足矣!_第43张图片

第陆部分:指针

一:指针类型和数组名

直指重要知识点的C语言笔记,这一篇足矣!_第44张图片

  1. 指针的基本概念
#include 
int main()
{
	int a = 10;//在内存中开辟一块空间
	int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//将a的地址存放在p变量中,p就是一个之指针变量。
	return 0;
}

void*是一种特殊的指针类型,只有对应的地址,没有内存的大小。
指针的类型决定了指针向前或者是向后走一步有多大(距离)。

#include 
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	char *str3 = "hello bit.";
	char *str4 = "hello bit.";
	if(str1 ==str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");
	if(str3 ==str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");
return 0;
}

直指重要知识点的C语言笔记,这一篇足矣!_第45张图片
其中:栈越往下地址越小,常量区的数据不能够修改。
str1 str2 这两者都是数组,在栈中会开数组长度的一个大小,将数组内容拷贝进去,而他们之中所存放的是首字母的地址,两者在栈之中地址是不同的,后进先出的原则,后面的地址会小一些。
str3 str4 这两者是指针 则只会开4个字节,存放的是首字母h的地址,两者是相同的。

  1. 指针和数组名
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址

二:指针运算

  1. 指针加减整数

直指重要知识点的C语言笔记,这一篇足矣!_第46张图片

  1. 指针减指针

指针相减

  1. 指针的关系运算

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

直指重要知识点的C语言笔记,这一篇足矣!_第47张图片

三:指针和数组的关系

  1. 指针访问数组

既然可以把数组名当成地址存放到一个指针中,那么我们就可以使用指针来访问一个数组。

#include 
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}

直指重要知识点的C语言笔记,这一篇足矣!_第48张图片
通过打印结果我们可以发现,p+i其实所计算的就是数组arr下标为i的地址,因此我们就可以直接通过指针来访问数组。

  1. 数组名和&数组名
    对于int arr[10];之中的,arr&arr分别是啥?
#include 
int main()
{
int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}

直指重要知识点的C语言笔记,这一篇足矣!_第49张图片
通过运行结果我们可以看的出来,两者所打印出来的地址是完全相同的,但是两者所表示的意义却是完全不同的。
直指重要知识点的C语言笔记,这一篇足矣!_第50张图片
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

四:二级指针和指针数组

1. 二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是 二级指针。
直指重要知识点的C语言笔记,这一篇足矣!_第51张图片

2. 指针数组和数组指针

int *p1[10];
int (*p2)[10];

判断是数组指针,还是指针数组:

  1. (看优先级来判断) 一般来说方括号[] 的优先级高一些。
  2. ()[ ] 是从左到右看!! 先确定的写在最后面(也就是按优先级顺序开始读,但是是从后往前开始书写)!!
  3. 第一个优先级是用来确认它到底是一个什么 ! int (*p[10])[5] 数组【5】 指针 数组【10】

指针数组是指针还是数组?
答案:数组,是存放指针的数组
int* arr[5];//是什么?arr是一个数组,有五个元素,每个元素是一个整形指针。

数组指针是指针?还是数组?
答案是:指针

相关一维指针学习列题:
直指重要知识点的C语言笔记,这一篇足矣!_第52张图片
直指重要知识点的C语言笔记,这一篇足矣!_第53张图片
相关二维指针学习练习:
直指重要知识点的C语言笔记,这一篇足矣!_第54张图片
直指重要知识点的C语言笔记,这一篇足矣!_第55张图片

3. 函数指针的调用和定义

在这里插入图片描述

//函数指针的定义和调用
typedef void(*P_FUNC)(int);

void f1(int a){
	printf("wolaile:%d\n",a);
}

void (*signal(int ,void(*)(int)))(int);
P_FUNC signal(int, P_FUNC);

int main(){
	void(*pf1)(int);
	P_FUNC pf2=f1;
	pf1(1);
	pf2(2);
	(*pf1)(3);
	(*pf2)(4);
	system("pause");
	return 0;
}

直指重要知识点的C语言笔记,这一篇足矣!_第56张图片

直指重要知识点的C语言笔记,这一篇足矣!_第57张图片

直指重要知识点的C语言笔记,这一篇足矣!_第58张图片

  1. const char* name=“chen”

name被定义为指向常量的指针,所以它所指的内容不能改变,但指针本身的内容可以修改,而name[3]=‘q’,修改了name所指的内容,是错误的;name==lin,name=new char[5],name=new char('q')以不同的方法修改了常指针,都是正确的。
(收藏起来之后慢慢看)
写在完结处的话:再一次重新整理后从原本的42426字改成了24818字,也是因为自己对于C语言的更加娴熟,因此对于一些杂糅的东西可以做到删除或者是增添,这样也能够更加基础和宽泛的将涉及到的众多基础和升华部分容做一次全面的整合,是帮助自己也是帮助别人,那些正在学习路上的朋友,未来的路还长一定要记得加油。

你可能感兴趣的:(C语言,结构体,指针)