一、结构体大小的计算及位段
(结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客)
1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少?
#pragma pack(4)
struct A
{
int a;
short b;
int c;
char d;
};
struct B
{
int a;
short b;
char c;
int d;
};
#pragma pack()
int main()
{
printf("%d\n",sizeof(A));//16
printf("%d\n",sizeof(B));//12
return 0;
}
2.在VS2022下,默认对齐数为8字节,这个结构体所占的空间大小是多少字节
typedef struct
{
int a;
char b;
short c;
short d;
}aa_t;
int main()
{
printf("%d\n",sizeof(aa_t));//12
return 0;
}
3.当A=2,B=3时,pointer分配了几个字节空间?
(位段的几个成员共用一个字节,如果一个字节内剩余的空间(比特位)不足以放下下一个成员,会浪费掉这些空间,重新在开辟一个字节来继续存放)
#define A 2
#define B 3
#define MAX_SIZE A+B
struct A
{
unsigned char a : 4;// 1byte
unsigned char b : 2; ————> 3byte
unsigned char c;// 1byte
unsigned char d : 1;// 1byte
};
int main()
{
struct A* pointer = (struct A*)malloc(sizeeof(struct A) * MAX_SIZE);
// sizeeof(struct A) * MAX_SIZE = 3*2+3 = 9
return 0;
}
当然我们也可以打印出来看看:
4.以下代码的结果是什么?
int main()
{
unsigned char puc[4];
struct S
{
unsigned char a;// 1byte
unsigned char b : 1;//1byte
unsigned char c : 2; ————> 2byte
unsigned char d : 3;
};
s = (struct S*)puc;
memset(puc,0,4); //00000000 00000000 00000000 00000000
s->a = 2;//010 //00000010 00101001 00000000 00000000
s->b = 3;//011-1 // 02 29 00 00
s->c = 4;//100-00
s->d = 5;//101
printf("%02x %02x %02x %02x\n",puc[0],puc[1],puc[2],puc[3]);// %02x 表示打印2位十六进制位
// 02 29 00 00
return 0;
}
二、枚举、联合体
(枚举、联合体 详解请看:自定义类型:枚举、联合_m语言定义枚举量-CSDN博客)
1.以下代码的结果是什么?
联合的大小至少是最大成员的大小,当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
union Un
{
short s[7];// 14 2/8-short类型的整型,大小为2字节,小于默认对齐数8,取2
int n;//4 4/8-取4 14/4 - 最大成员的大小并不是最大对齐数的整数倍,因此,最终是取值为16
};
int main()
{
printf("%d\n",sizeof(union Un));//16
return 0;
}
2.以下代码的结果是什么?
枚举成员如果不赋初始值,默认从0开始,每增加一次加1,如果中间有成员进行赋值,那么在这个成员之前的就还是从0开始,在这个成员之后的,就在赋值的数的基础上每次增加1
enum e
{
x1,
y1,
z1 = 255,
a1,
b1
};
int main()
{
enum e A = y1;
enum e B = b1;
printf("%d %d\n",A,B);//1 257
return 0;
}
三、动态内存函数分析错误并改正
(详解请看:动态内存管理2+柔性数组-CSDN博客
动态内存管理-CSDN博客)
指出以下代码错误的地方并进行改正
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test()
{
char* str = NULL:
GetMemory(str);
strcpy(str,"hello world");
printf(str);
}
int main()
{
Test();
return 0;
}
错误:1.这个代码传给GetMemory函数的参数是值传递,而我们知道对形参的改变是不会影响实参的,所以即使GetMemory函数返回,str依然为NULL
2.使用动态内存函数进行空间开辟,没有对malloc函数的返回值进行判断
3.存在内存泄露,使用完之后没有进行free释放
改正:
void GetMemory(char** p) //使用一个二级指针变量进行接收
{
*p = (char*)malloc(100);
}
void Test()
{
char* str = NULL:
GetMemory(&str);//改为址传递(一级指针)
if(str != NULL) //对返回值进行判断
{
strcpy(str,"hello world");
printf(str);
}
free(str); //释放
str = NULL;
}
int main()
{
Test();
return 0;
}
四、大小端
在X64环境下,小端字节序存储,以下代码输出的结果是什么?
int main()
{
union
{
short k;
char i[2];
}*s,a;
s = &a;
s->i[0] = 0x39;
s->i[1] = 0x38;
printf("%x\n",a.k);//3839
return 0;
}
五、找单身狗
一个数组中只有两个数字是出现一次,其他所有数字都出现了两次。
编写一个函数找出这两个只出现一次的数字。
版本1:有一个数组,其中只有一个数字出现1次,其余数字都是出现2次
例如:1 2 3 4 5 1 2 3 4
版本2:一个数组中只有两个数字出现一次,其他数字都出现两次
例如:1 2 3 4 5 1 2 3 4 6
而我们要解决的就是版本2的内容
可以将这个数组分成两个数组,分别找出两个数组各自数字中出现一次的数字
//1 1 3 3 5
//2 2 4 4 6
或者
//1 1 4 4 5
//2 2 3 3 6
分组要领:就是要将2个单身狗必须放在2个组里面,同时每个组剩余的数字都是成对出现的
具体如何分组:将单身狗5和6异或(相同为0,相异为1)
// 5^6
//101
//110
//011
【1】如果我们将二进制最低位是1的数字放在一组,其他数字放在另一组:
//5 1 1 3 3
//6 2 2 4 4
【2】将将二进制第二位是1的数字放在一组,其他数字一组:
//5 1 1 4 4
//6 2 2 3 3
代码具体实现:需要一个整型数组来存放所有的数字,再有一个数组存放找到的两个单身狗,写一个函数来寻找单身狗
void find_single_dog(int arr[],int sz,int single_doe[])
{
int ret = 0;
int i = 0;
for(i = 0;i < sz; i++)
{
ret ^= arr[i];/所有数字二进制相互进行异或之后最终的结果是 5^6 的结果(因为成对出现的数字相互抵消,仅剩5和6未被抵消),此时ret=5
}
//计算ret的哪一位二进制和位是1
int pos = 0;
for(i = 0;i < 32; i++)
{
if(((ret >> i) & 1) == 1)
{
pos = i;
break;
}
}
for(i = 0;i < sz; i++)
{
if(((arr[i] >> pos) & 1) == 1)
{
single_dog[0] ^= arr[i];//最终数字的二进制之间异或的结果是101 - > 5
}
else
{
single_dog[1] ^= arr[i];//6
}
}
}
int main()
{
int arr[] = {1,2,3,4,5,1,2,3,4,6};
int sz = sizeof(arr)/sizeof(arr[0]);
int single_dog[2] = {0};
find_single_dog(arr,sz,single_dog);
printf("%d %d\n", single_dog[0], single_dog[1]);//5 6
return 0;
}
六、模拟实现atoi
atoi的作用:将字符串转换为整数
int atoi (const char * str);
- 将 C 字符串 `str` 进行解析,把其内容解读为一个整数值,并以 `int` 类型的值返回。
- 该函数首先会尽可能多地丢弃空白字符(就像 `isspace` 函数所判定的那样),直到找到第一个非空白字符。接着,从这个字符开始,它会处理一个可选的初始正负号,随后尽可能多地读取十进制数字,并将它们解读为一个数值。
- 字符串中在构成整数的字符之后还可以包含其他字符,这些字符会被忽略,并且不会对该函数的行为产生影响。
- 如果 `str` 中第一个非空白字符序列不是一个有效的整数,或者由于 `str` 为空,或者它仅包含空白字符而不存在这样的序列,那么将不执行转换操作,函数会返回零。
- 如果转换成功,该函数会将转换后的整数值以 `int` 类型返回。
- 如果转换后的值超出了 `int` 类型所能表示的值的范围,则会导致未定义行为。
首先我们先通过使用atoi这个函数,确定模拟实现它有哪些限制条件
#include <stdlib.h>
#include <stdio.h>
int main()
{
//1
int ret = atoi(" -12#34");//打印 -12
//2
int ret = atoi(" 1234");//打印 1234(前面空白字符不打印)
//3
int ret = atoi(" -1234");//打印 -1234
//4
int ret = atoi(" -1234111111111111111111111111111111111111111111111");//打印-2147483647
//5
int ret = atoi(NULL);//报警告
printf("%d\n", ret);
return 0;
}
最终我们确定限制条件(非法条件):
1.空指针(不能对空指针进行应用)
2.空白字符(不将空白字符打印出来)//" "
3.+/- 打印出来,其他的符号不考虑
4.考虑溢出(字符串太长)
模拟实现:
#include <assert.h>
#include <ctype.h>
enum State
{
VALID,
INVALID
}state = INVALID;//默认表示非法
int my_atoi(const char* str)
{
assert(str);
//检查字符串指针是否有效,处理空字符串返回0
if(*str == '\0')
{
return 0;
}
//跳过空白字符
while(isspace(*str))
{
str++;
}
//处理正负号(其他符号忽略),设置flag为1或-1
int flag = 1;
if(*str == '+')
{
str++;
}
else if(*str == '-')
{
int flag = -1;
str++;
}
//遍历后续字符,将数字转换为整数,同时考虑符号和溢出
long long ret = 0;
while(*str)
{
if(isdigit(*str))
{
ret = ret * 10 + (long long)(flag * (long long)(*str - '0'));//将一个个字符转换成整数
if(ret > INT_MAX)
{
return INT_MAX;
}
else if(ret < INT_MIN)
{
return INT_MIN;
}
}
else
{
return (int)ret;
}
str++;
}
state = VALID;
return (int)ret;
}
int main()
{
int ret = my_atoi(" -1234");
if (state == VALID)
printf("%d\n", ret);
else
printf("非法字符串转换,%d\n", ret);
return 0;
}
七、文件操作
下面程序的功能是什么?
统计文件的字符数
int main()
{
long num = 0;
FILE* pf = NULL;
if((pf = fopen("fname.dat","r")) == NULL)
{
perror("fopen");
return 1;
}
while(fgetc(pf) != EOF)
{
num++;
}
printf("%d\n",num);
fclose(pf);
pf = NULL:
return 0;
}