C语言相关概念和易错语法(7)

1.memcpy

标准格式:

void * memcpy ( void * destination, const void * source, size_t num );

(1)注意memcpy是无脑复制粘贴的,就算越界访问

(2)复制粘贴的区域不要有重复的,否则会出现数据错误,一般情况下建议使用memmove,你大可以将memcpy想象成低级的memmove

下面是memcpy的模拟实现:



#include <stdio.h>

#include <assert.h>

void* my_memcpy(void* arr2, const void* arr1, size_t num)
{
	assert(arr1 && arr2);

	void* ret = arr2;

	while (num--)
	{
		*((char*)arr2)++ = *((char*)arr1)++;
	}

	return ret;
}


int main()
{
	char arr1[] = "Hello,world!";

	char arr2[20] = { 0 };

	printf("%s\n",(char*)my_memcpy(arr2, arr1, 11));


	return 0;
}

2.memmove 

标准格式:void * memmove ( void * destination, const void * source, size_t num );

(1)在一般情况下,可以将memmove视作更为强大的memcpy,memmove可以复制重合的内容,所以一般情况下避免使用memcpy

相关代码的实现如下:



#include <stdio.h>

#include <assert.h>


void* my_memmove(void* arr2, void* arr1, size_t num)
{
	assert(arr1 && arr2);

	int i = 0;


	if (arr2 >= arr1)//源字符串的地址较低,从后侧开始
	{
		for (i = 1; i <= num; i++)
		{
			*((char*)arr2 + num - i) = *((char*)arr1 + num - i);

		}
		
	}
	else
	{
		for (i = 0; i < num; i++)
		{
			*((char*)arr2 + i) = *((char*)arr1 + i);
		}

	}

	return arr2;

}



int main()
{

	char arr[20] = "Hello,world!";

	printf((char*)my_memmove(arr + 3, arr, 5));

	return 0;
}

3.memset

标准格式:void* memset(void* ptr, int value, size_t num);

实现代码如下:


#include <stdio.h>

#include <assert.h>

void* my_memset(void* arr, int i, size_t num)
{
	assert(arr);

	int m = 0;

	for (m = 0; m < num; m++)
	{
		*((char*)arr + m) = i;

	}

	return arr;

}


int main()
{

	char arr[20] = "Hello,world!";

	printf((char*)my_memset(arr, '-', 5));


	return 0;
}

注意这里的形参虽然接受的是个整形,但字符本质上也是以ASCII码的形式存储的,所以这里能够传入字符,且在赋值时会自动进行类型转换。

4.char与unsigned char

(1)char

由于char 只有8个bit位的存储空间,所以它最多能表示256个数字(包含0),但在signed char中,由于存在原反补的存储规则,导致01111111是最大的正数,对应过来是127,如果再加,就会是10000000,除符号位按位取反+1得到1 00000000,截断后本应是0,但这和0000000冲突了,也就是说0会出现两次,因此规定在这种情况下,127+1 = -128。再往后+1得10000001,除符号位按位取反得11111110,+1得11111111,对应的就是-127,以此类推。

这样会出现一些案例,如:


#include <stdio.h>

#include <string.h>

int main()
{
	char a = 127;

	char arr[1000] = { 0 };
	
	int i = 0;

	for (i = 0; i < 1000; i++)
	{
		arr[i] = a + i;
	}

	printf("%zu\n", strlen(arr));

	return 0;
}

最终输出的结果是

这里实际装进数组的对应字符的ASCII码值是127,-128,-127......-1,0,.....

实际识别到0(也就是\0)就停止了,因此用strlen识别出的大小是129

(2)unsigned char

在这里,符号位的概念不复存在,8个bit位都被视作数值位,因此最高可以表255,再+1就会循环。

5.浮点数存储的注意事项:

注意存储的顺序分别是:浮点数的符号(0为正,1为负),指数位(无符号型),有效数字

(1)指数位:注意这是以二进制存储,底数为2,指数位会加上一个中间值来保证不会出现负数指数的情况,因此要主动换算一次才能得到真实的指数位

(2)有效数字,一般情况下,科学计数法的有效数字都是1.xxxx形式,所以自动忽略1,回算时再主动添加上去,但如果指数位全为0,回算时加0,这代表无穷小,也就是0。如果指数位全为1,表正负无穷大

(3)对于32位浮点数,1bit给符号位,8bit给指数位(中间值127),其余23bit给有效数字

对于64位浮点数,1bit给符号位,11bit给符号位(中间值1023),其余52bit给有效数字

(4)一般自己书写时的逻辑是符号位,有效数字,指数位。但存储顺序不一样,这点要注意!

6.结构体,联合体,枚举变量,柔性数组的用法

这四个知识点比较简单,这里用作复习

下面看一段代码:




#include <stdio.h>

#include <stdlib.h>

#include <assert.h>


enum sex
{
	male,

	female,

	secret
};

struct S
{
	union m
	{
		int i;

		char name[20];

		char id[30];

	};

	enum sex j;

	int arr[0];

};

int main()
{

	int m = 0;

	printf("结构体的大小是%zu\n", sizeof(struct S));

	printf("联合体的大小%zu\n", sizeof(union m));

	struct S* s = (struct S*)malloc(sizeof(struct S) + sizeof(int) * 10);

	assert(s);

	s->i = 20;

	s->j = male;

	for (m = 0; m < 10; m++)
	{
		s->arr[m] = m;
	}

	for (m = 0; m < 10; m++)
	{
		printf("%d ", s->arr[m]);
	}


	free(s);

	s = NULL;


	return 0;
}

注意事项:(1)结构体可以和union等嵌套使用

(2)enum的成员列表中的成员之间用','(逗号)隔开

(3)malloc要和free和NULL联合使用,避免内存泄漏,野指针的情况发生

(4)s->arr这种操作在语法上不被允许,只允许对arr[0]等逐一修改,因此这段代码避免了这种写法

(5)union的大小必须满足是最大对齐数的整数倍,即union也存在对齐现象

(6)柔性数组必须在结构体的最后一个元素,和内存开辟函数结合使用,结构体实质上是在堆上创建空间的

7.流

流是将程序和输入输出设备联系起来的媒介,输入设备比如键盘、数据文件(包含文本文件和二进制文件)等,输出设备比如控制台、数据文件等。

我们平时使用的scanf就是将键盘上的内容输入到流里,再通过流(FILE* stdin)读到程序中

printf就是将程序中的内容写到流(FILE* stdout)里,再通过相应操作打印到控制台的屏幕上。

我们不需要关注流与输入输出设备之间的联系方式,只需关注我们要从流中得到什么信息和输入什么信息到流中。

C语言在程序运行时会默认打开stdin,stdout,stderr,我们日常所用的printf,scanf,gets等都是我们和流进行交互,只是C语言底层帮我们实现了很多东西,所以我们之前没有感知。

流是一种文件指针,文件指针实质上是一种结构体指针的重命名,它们指向的结构体包含了对应输入或输出设备的信息,比如stdin这个结构体就包含了键盘的一些信息,也包含了键盘和程序进行沟通的信息等。

流可以以指针的形式对对应结构体进行操作,从而实现对输入输出设备的操作。这部分不需要关心,这是在C语言底层实现的。

这个概念的理解帮助我们对文件操作和程序运行逻辑的理解。

8.fprintf,sprintf和fscanf,sscanf的理解和使用

(1)fprintf 和 fscanf

这里就是必须要在理解流的概念之后才会理解这个函数。

fprintf,fscanf只有第一次参数多了一个FILE*的变量,也叫流的种类,这两个函数适用于任何流,同样的还有fgets,fputs,fgetc,fputc

fscanf会根据不同的流将对应设备中的数据读到程序中,这里stdin对应的设备是键盘

fprintf会根据不同的流将数据写到流对应的设备中,在这里stdout对应的设备是控制台的屏幕

(2)sprintf 和 sscanf

用通俗的话来说,sprintf是将格式化的数据打印成字符串,sscanf是将字符串中的数据提出来,变成格式化的数据,这样有助于理解

sprintf必须加上“打印”成字符串后保存该字符串的首元素地址

sscanf必须加上“提取”格式化数据所对应字符串的首元素地址

下面来看一组代码加强理解:




#include <stdio.h>

#include <stdlib.h>


enum sex
{
	male,

	female,

	secret,

};


struct Stu
{
	char name[20];

	int age;

	enum sex s;

}s1;


int main()
{

	char arr[100] = { 0 };

	char inf[30] = "Lihua 18 male";

	sscanf(inf, "%s %d %d", s1.name, &(s1.age), &(s1.s));

	fprintf(stdout, "%s %d %d\n", s1.name, s1.age, s1.s);

	sprintf(arr, "%s %d %d", s1.name, s1.age, s1.s);

	fprintf(stdout, arr);

	return 0;
}

运行的结果是

这里的逻辑就是将一串字符提取成格式化数据后,打印该格式化数据;再从格式化数据打印到另一个字符串中,最后再打印这个字符串。

值得注意的是,enum赋值时必须按照其成员的名字命名,而不能是数字,虽然在这里0代表male,但赋值的时候必须以male的输入方式赋值。

9.文件操作中常见的函数

这几个函数在文件操作里面比较常见,但其实比较简单,下面通过代码了解它们的用法:


#include <stdio.h>

#include <errno.h>

#include <stdlib.h>

int main()
{

	char arr[20] = { 0 };

	FILE* pf = fopen("./data.txt", "w+");

	if (!pf)
	{
		perror("fopen");

		return EXIT_FAILURE;//1,需要<stdlib.h>
	}

	fputc('H', pf);

	fputs("ello,world!", pf);

	fseek(pf, 0, SEEK_END);

	int sz = (int)ftell(pf);//long int类型

	printf("文件内容大小是%d\n", sz);

	rewind(pf);

	arr[0] = fgetc(pf);//返回的是拷贝的字符的ASCII码值,返回int

	fgets(arr + 1, sz, pf);//get的个数是sz-1,最后有\0

	printf(arr);

	fclose(pf);

	pf = NULL;

	return 0;

}

(1)还有fprintf 和 fscanf也比较常见,但上面一条已经讲解,所以这里不再举例,只需要把首个参数从stdin或stdout换成文件操作流即可

(2)rewind(pf)是还原打开文件后光标的起始位置。当然, 也可以用 fseek(pf, 0, SEEK_SET)代替。而ftell是用来告诉此刻光标的偏移量的,这三个函数通常相互配合使用。

(3)fputc标准格式:int fputc ( int character, FILE * stream );

fputs标准格式:int fputs ( const char * str, FILE * stream );

fgetc标准格式:int fgetc ( FILE * stream );

fgets标准格式:char * fgets ( char * str, int num, FILE * stream );

还有两个二进制文件操作相关的函数:

fread标准格式:size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );

fwrite标准格式:size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );

这里要从右向左读:在stream这个流里,对count个size大小的数据进行读(写)操作。

这些函数都很容易理解,主要是注意参数的位置

(4)创建文件时前缀./表示在当前目录,./../表示在上级目录(../表示上级目录),可以嵌套使用,如./../.././../(但逻辑得自己捋清楚)

注意的是在绝对路径下表示文件地址的是\而不是这里的相对路径的/,两者有细微区别,注意细节

10.文件读取结束后的判断

一般情况下,我们使用feof和ferror来判断读取文件结束后是什么原因


#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

int main()
{

	int i = 0;

	FILE* pf = fopen("./data.txt", "r");

	if (!pf)
	{
		perror("fopen");

		return EXIT_FAILURE;

	}

	fprintf(pf, "Hola!");

	while (fgetc(pf) != EOF);
	
	if (feof(pf))
	{
		printf("成功读取完数据并退出\n");
	}
	else if (ferror(pf))
	{
		printf("读取时遇到了错误导致退出\n");
	}

	fclose(pf);

	pf = NULL;

	return 0;
}

在调用了一个函数后,文件信息区的信息会被更改,所以使用这两个函数直接传一个参数即可

  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值