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、枚举的优缺点
优点:
- 易读性
- 类型安全
- 可拓展性
缺点:
- 灵活性低
- 占用内存
- 兼容性差
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;
}
让文件指针的位置回到文件的起始位置
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;
}