C语言模糊不清的知识

发布于:2025-05-11 ⋅ 阅读:(16) ⋅ 点赞:(0)

1、malloc、calloc、realloc的区别和用法

  • malloc实在堆上申请一段连续指定大小的内存区域,并以void*进行返回,不会初始化内存。
  • calloc与malloc作用一致,只是calloc会初始化内存,自动将内存清零。
  • realloc用于重新分配之前通过malloc、calloc或realloc分配的内存块。它允许改变已分配内存块的大小,并根据需要调整内存块的位置。

2、malloc和new的区别?new的实现?malloc的底层实现

 malloc是C库中开辟内存的函数

new为C++关键字,是封装之后的,与类和对象结合更紧密。new是由operator new封装而来,operator new获取原始内存与malloc类似。

malloc底层实现:是调用系统的接口,小内存块利用堆扩展高效管理,大内存用mmap,减少碎片。

3、整型不同类型间互相赋值和提升问题

整型提升:当较小的整型类型(char、short)参与表达式运算时,会被自动提升为int或unsigned int

赋值类型转换:较大类型赋值给较小类型会发生截断

int a = 0x12345678;
short b = a; // b = 0x5678
char c = a; //c = 0x78

较小类型赋值给较大类型会发生扩展

char a = -10; //0xF6
int b = a;    //b = 0xFFFFFFF6

4、&数组名和数组名分别代表什么意义

数组名是以单个元素为单位,&数组名是以整个数组为单位,他们的数组首地址相同

  • 数组名可以看作是指向数组首地址的指针。
  • &数组名指的是产生指向整个数组的指针。

5、当strlen遇上转义字符

int main()
{
	char s[] = "\\123456\123456\t";
	printf("%d\n", strlen(s));
	return 0;
}

\\为一个转义字符算1个字节,123456算6个字节,\123是一个八进制的转移字符算1一个字节,剩下的456\t算4个字节,总共是12个字节。

6、strstr函数和strcat函数

strstr() 是 C 标准库中的一个字符串处理函数,用于在一个字符串中查找子字符串的第一次出现位置。

strstr函数实例如下

手撕如下:

char* mystrstr(const char* p1,const char* p2)
{
    char* cp = p1;
    char* s1 = p1;
    char* s2 = p2;
    while(*cp)
    {
        s1 = cp;
        s2 = p2;
        while(s1 && s2 && *s1 == *s2)
            {
                s1++,s2++;
            }
        if(*s2 == '\0')
            return cp;
        cp++;
    }
    return NULL;
}

 strcat函数

C 库函数 char *strcat(char *dest, const char *src) 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。

7、枚举的优缺点

优点:

  1. 易读性
  2. 类型安全
  3. 可拓展性

缺点:

  1. 灵活性低
  2. 占用内存
  3. 兼容性差

8、文本文件和二进制文件

  • 数据以二进制存储,不加转化输出为二进制文件。
  • 字符数值型数据以ASCII存储的文件是文本文件。

9、编译的具体细节

1、预处理

展开头文件,替换宏定义,取消注释,预处理之和的代码文件以.i文件结尾

2、编译

根据语法分析语义分析形成汇编语言,文件以.s文件结尾

3、汇编

根据汇编语言汇编指令识别代码形成目标文件,文件以.o文件结尾

4、链接

将多个目标文件和库文件合并形成可执行文件,链接分为动态链接和静态链接,静态链接是直接将库文件代码下载到可执行文件里,动态链接根据运行逐步加载。

最后形成.exe文件或库文件.so .dll

10、volatile、extern、static、typedef的作用

volatile一般用来修饰变量,是为了告诉编译器禁止对变量进行不必要的优化

例如在一个这样的代码里

// 假设 addr 是硬件寄存器地址
int* addr = (int*)0x1234; 
int a = *addr; // 第一次读取寄存器
int b = *addr; // 编译器可能优化为 b = a(假设寄存器值未变)

加入volatile之和

volatile int* addr = (volatile int*)0x1234; 
int a = *addr; // 强制从内存(寄存器)读取
int b = *addr; // 再次强制读取,确保值真实

 在多线程中变量可能呗缓存到CPU寄存器或高速缓存中,导致线程间数据不一致。

加入volatile确保对变量操作直接操作主内存而非缓存。但不保证原子性。

extren用来声明外部变量或函数,告诉编译器声明外部变量或函数

1、跨文件共享变量

2、声明外部函数

与static的区别是extern可以跨文件访问

static用于修饰变量或函数,改变其作用域和生命周期。

static修饰的全局变量的链接属性为内部链接,仅限当前文件可见,禁止跨文件访问。

typedef用于创建类型别名的关键字,仅定义类型别名,不分配内存。这是编译器的行为,有类型的检查,作用域受限。

define是预处理替换,无类型概念,全局有效。

11、柔性数组

柔性数组即数组大小待定的数组,在 C 语言中可以在结构体的最后一个元素使用长度为 0 的数组来实现。

在 C 语言中,柔性数组是一种特殊的数组定义方式。它通常作为结构体的最后一个成员出现,其形式为一个未指定长度的数组类型

在使用柔性数组时,需要我们手动开辟内存空间,并且对内存进行调整,我们用mallohenc进行内存的开辟,使用realloc对内存进行调整扩充。

​
struct S
{
	int n;
	int arr[];//未知大小的数组 - 柔性数组成员
};
int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 10*sizeof(int));
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	ps->n = 10;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	free(ps);
	ps = NULL;
	return 0;
}

​

12、文件操作fsee、ftell、rewind、feof函数

1.fseek

int fseek(FILE* stream,long int offset,int origin);
//第一个参数文件流
//第二个参数偏移量
//偏移量如果是正数,向后偏移
//偏移量如果是负数,向前偏移
//第三个参数起始位置

fseek从当前光标指向的流中获取字符
SEEK_SET:文件的起始位置
SEEK_CUR:文件指针当前位置
SEEK_END:文件的结束位置
在这里插入图片描述

int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读
	char ch = fgetc(pf);
	printf("%c\n", ch);//a

	fseek(pf, 4, SEEK_SET);
	//距文件起始位置4个偏移量
	printf("%c\n", fgetc(pf));//e
	
	//fseek(pf,5,SEEK_CUR);
	//距当前文件位置5个偏移量
	//printf("%c\n", fgetc(pf));//g
	
	//fseek(pf,-6,SEEK_END);
	//距文件末尾位置6个偏移量
    //printf("%c\n",fgetc(pf));//d
     
	fclose(pf);
	pf = NULL;

	return 0;
}

这是test.txt中的字符串
在这里插入图片描述 

 2.ftell

返回文件指针相对于起始位置的偏移量

long int ftell(FILE* stream);
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读
	char ch = fgetc(pf);
	printf("%c\n", ch);//a

	int ret = ftell(pf);
	printf("%d\n", ret);//1

	fclose(pf);
	pf = NULL;

	return 0;
}

在这里插入图片描述

3.rewind

让文件指针的位置回到文件的起始位置

void rewind(FILE* stream);

 

int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读
	char ch = fgetc(pf);
	printf("%c\n", ch);//a

	int ret = ftell(pf);
	printf("%d\n", ret);//1

	rewind(pf);
	//文件指针回到起始位置
	printf("%c\n",fgetc(pf));//a
	//打印a说明文件回到了起始位置

	fclose(pf);
	pf = NULL;

	return 0;
}

 

在这里插入图片描述

二.文件读写结束的判定

文件现在读取结束了,但是是什么原因读取结束的呢?
1.可能是遇到了文件末尾
2.可能是读取的时候发生了错误

1.feof函数(不是用来判定文件结束的)

文件已经结束,用来判定文件结束的原因
feof的作用是:文件读取结束的时候,判断文件是否遇到文件末而结束的
feof没有读到文件末尾返回0,读到文件末尾返回非0值

int feof(FILE* stream)
//feof函数只需要关注传入的文件指针是否为NULL即可

2.ferror函数

文件已经结束,ferror读取的时候没有发生错误返回0,发生错误返回非0值

int ferror(FILE* stream)
//ferror函数只需要关注传入的文件指针是否为NULL即可

下面来举一个例子:

int main()
{
	FILE* pf = fopen("test.txt", "r");
	//打开文件
	if (pf == NULL)
	{
		perror("fopen");
		//打印错误
		return 1;
		//发生错误提前返回
	}
	//使用
	char ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c ", ch);
	}
	printf("\n");
	if (feof(pf))//返回非0值
	{
		printf("读到文件末尾而结束\n");
	}
	else if(ferror(pf))//返回非0值
	{
		printf("读取错误而结束\n");
		perror("ferror");
	}
	fclose(pf);
	//关闭文件
	pf = NULL;

	return 0;
}