C语言进阶及热点知识归纳

2 篇文章 0 订阅
1 篇文章 0 订阅

在这里插入图片描述

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

一:C语言程序设计

  1. 两种头文件的介绍
    #include < > 在系统目录中查找头文件
    #include “ ” 先在当前项目目录中查找,若没有找到再去系统目录中查找
    这种做法是为了更好防止头文件被重复包含。因此一般使用包含标准库头文件就使用 <>,包含自己定义的头文件使用 “ ”
  2. 冯诺依曼体系
    冯诺依曼体系也被放在大学计算机的第一课来介绍
    冯诺依曼体系

二:数据类型

1. 数据大小及范围

不同的系统所对应的数据类型大小是不同的:
C语言中各类型数据的长度
主要数据的范围

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

例如:char的取值为0-255 则如果是它的数组,所能够容纳的值就只有256个
char的值直接决定了

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、在赋值运算中,赋值号两边数据类型不同时,赋值号右边的类型将转换为左边的类型。如果右边的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度,丢失的部分直接舍去。
一些需要注意的东西:
类型转换的规则
类型转换阶层图
小技巧:想要实现运算之后的四舍五入计算,只需要给一个数加上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);

转换步骤
例题3. 无符号0减去有符号-1

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

转换步骤

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垂直制表符
\dddddd表示1~3个八进制的数字。 如: \130
\xdddddd表示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;

宏和函数的对比
在这里插入图片描述
在这里插入图片描述
宏续接
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. While中的continue是终止本次循环,也就是本次循环中continue后的代码不再执行,直接跳转到while语句的判断部分,进行下一次循环入口判断
  3. 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.实际参数(实参)真实传给函数的参数

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

形式参数指函数名后括号中的变量,形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。当函数调用完成之后形参自动销毁,因此形式参数只在函数中有效
实参和形参的比较
通过函数调用,函数中拥有和实参一模一样的内容,所以可以简单认为:实参实例化之后相当于实参的一份临时拷贝

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

传值调用
函数的形参和实参分别占有不同的内存块,对形参的修改不会影响实参

传址调用
传址调用是把函数外部创建的变量内存地址传递给函数作参数的一种调用方式,这种方式可以让函数和函数外的变量建立真正联系,也就是函数内部可以直接操作函数外部的变量

3. 函数的声明和定义

函数的声明

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

函数的定义:
1.指函数具体实现,交代函数的实现功能

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

  1. 什么是递归?
    程序调用自身的编程技巧称之为递归
    递归思想:大事化小,小事化了!

  2. 递归的两个必要条件

  • 存在限制条件,当满足这个限制条件的时候,递归不再继续!
  • 每次递归调用之后越来越接近这个限制条件,这是它的收敛性
    递归程序是对正在进行的问题进行不断地细化和拆分
  1. 递归所产生的栈溢出
    系统分配给程序的栈空间是有限的,如果出现死循环,或死递归可能不断开辟栈空间导致栈空间耗尽,将这样的现象称为栈溢出

  2. 解决递归溢出

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

二:数组

一组相同类型元素构成的集合

  1. 数组使用下标来访问,下标从0开始
  2. 数组的大小通过sizeof 计算得到,是多少就是多少
  3. 数组在内存中是连续存放的
  4. 计算字符数组长度的话,将“\0”计算在内
  5. sizeof 是一个运算符,具有一个重要的特性
    在这里插入图片描述

1. 数组作为函数参数

在写代码时候,会将数组作为参数传入函数

#include <stdio.h>
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(结构体)的话,其中大小计算不包含柔性数组。
柔性数组

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

一:结构体

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. 为什么要有内存对齐
    实例图 让占用空间小的成员尽量集中在一起, 以空间来换取时间!

二:枚举

枚举:一个定义常量的集合
默认枚举里所列举出的第一个值是零,如果第一个所举出来的值赋予5,则表示从5往后走
枚举

  1. enumint是否应该划上等号呢?
    在C语言之中,enumint是等价的,枚举的目的为了代码的可读性更好一点;
    enum所表示出来的含义是不应该和整数划等号的;但枚举在内存中的存储和int一样,都以字节序+原码反码补码的形式进行存储

三:联合(共用体)

特殊的自动类型,里面成员共用一块空间,谁大就用谁的空间,当最大成员大小不是最大对齐数整数倍时,要对齐到最大对齐数的整数倍(最特殊的例子:ip的使用
联合

四:位段

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

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位
    在这里插入图片描述

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

一:字符串函数

  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的代码实现
在这里插入图片描述

  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 <stdio.h>
#include <assert.h>
#include <string.h>
// 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 <stdio.h>
#include <assert.h>
#include <string.h>

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分割函数
    在这里插入图片描述
    分割函数
  2. memcpy函数:拷贝内存
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
// 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函数,移动解决内存重叠的问题
    在这里插入图片描述
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;
}

memmove函数

二:内部存储结构

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 <stdio.h>

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

ASCII 是字母和数字在内存之中的十进制地址值

三:动态内存管理

1. 内存区域划分

内存区域划分
在这里插入图片描述

2. 动态内存管理

动态内存管理

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2 calloc
calloc void* calloc(size_t num, size_t size);把num个大小为size的元素开辟一个空间,值全部为0
3. recallo
void* realloc(void* ptr,size_t size); 把一个曾经已经申请到的内存进行扩容

Realloc 扩容重新开个大的,将原来的内容拷贝过去后将原空间释放(后面如果有足够地空间,直接扩容,如果没有就找足够地空间)
在这里插入图片描述
扩容

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

在这里插入图片描述

  • 非动态内存不能释放,一块内存不能释放多次
  • 程序结束之后申请的内存会自动泄露
  • 内存泄漏是指针丢了,内存是不会丢的
  • 长期的内存泄漏会导致运行越来越慢最终挂掉
    在这里插入图片描述

四:文件操作

1. 文件

文件在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 文件的读写

文件的使用方式
在这里插入图片描述
文件的随机读写代码

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 <stdio.h>", 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);
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
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. 文件编译过程
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    编译阶段进行的内容: const ,constexpr, template, type_tarits ,sizeof, #define .

  2. 条件编译
    在这里插入图片描述

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

在这里插入图片描述

第陆部分:指针

一:指针类型和数组名概念

背下来的知识点

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

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

#include <stdio.h>
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;
}

例题内存图
其中:栈越往下地址越小,常量区数据不能够修改
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. 指针加减整数
    在这里插入图片描述

  2. 指针减指针
    指针相减

  3. 指针的关系运算
    允许指向数组元素的指针与指向数组最后一个元素后的内存位置指针比较,但不允许与指向第一个元素之前的内存位置指针进行比较
    在这里插入图片描述

三:指针和数组的关系

  1. 指针访问数组
    既然可以把数组名当成地址存放到一个指针中,那就可以使用指针来访问一个数组
#include <stdio.h>
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;
}

打印地址图
通过打印结果可以发现,p+i其实所计算的是数组arr下标为i的地址,因此可以直接通过指针来访问数组

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

运行结果
通过运行结果可以看出,两者所打印的地址完全相同,但两者所表示的意义却完全不同
在这里插入图片描述
实际: &arr 表示的是数组地址,不是数组首元素的地址。(细细体会一下)
数组地址+1,跳过整个数组大小,所以 &arr+1 相对于 &arr 的差值是40.

四:二级指针和指针数组

1. 二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是二级指针
二级指针

2. 指针数组和数组指针

int *p1[10]; //指针数组
int (*p2)[10];//数组指针

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

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

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

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

相关一维指针学习列题:
在这里插入图片描述
在这里插入图片描述
相关二维指针学习练习:
在这里插入图片描述
在这里插入图片描述

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;
}

在这里插入图片描述
2.
在这里插入图片描述
3.
在这里插入图片描述
4. const char* name=“chen”
name被定义为指向常量的指针,所以它所指的内容不能改变,但指针本身的内容可以修改,而name[3]=‘q’,修改了name所指的内容,是错误的;name==lin,name=new char[5],name=new char('q')以不同的方法修改了常指针,都是正确的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值