1.大小和长度竟然不是一个意思
int main(void)
{
char s[] = "I love Linux\0\0\0";
int a = sizeof(s);
int b = strlen(s);
printf("%d %d\n", a, b);
}
考察点:函数sizeof与strlen的区别
对应知识点:sizeof表示大小即所占字节数(含\0)而strlen表示长度即共有几个字符(不含\0,读到\0即停止)
注意:计算时都应该包含空格
答案:a 16 b 12
原因:char类型所占一个字节,加上自动字符串中最后一位自动补上的\0该字符数组中共有16个元素。而函数strlen读到第一个\0就停止,应此长度仅有前边的字母及空格部分。
2.箱子的大小和装有物品的顺序有关
struct test1 {
int a;
short b;
double c;
};
truct test2 {
short b;
int a;
double c;
};
int main(void)
{
struct test1 t1;
struct test2 t2;
printf("sizeof (t1) : %d\n", sizeof(t1));
printf("sizeof(t2): %d\n", sizeof(t2));
}
考察点:结构体与联合体的大小运算
对应知识点:详见上篇2022年纳新题解
答案:16 16
原因:其中最宽成员变量均为double,该变量所占字节数为八个字节。所有成员变量所占字节数相加为14。保持为最宽成员变量的倍数即为16。且因为short占两字节无论如何排列均无法与int相接凑成8字节,因此排列顺序对其并无影响。
3.哦,又是函数
考察点:函数的定义与使用
对应知识点:详见上篇2022纳新题解
#include<stdio.h>
#include<stdlib.h>
void func(int a[][13], int m, int n);//若函数定义于主函数之后,需将函数头在主函数前注明(不要忘记分号)
int main()
{
int arr[10][13];
for(int i = 0;i<10;i++){
for(int j = 0;j<13;j++){
arr[i][j] = rand();//读入数组的值
}
}
func(arr,10,13);
return 0;
}
void func(int a[][13], int m, int n)//函数定义时函数头后并不需要添加分号
{
for(int i = 0;i<m;i++){
for(int j = 0;j<n;j++){
printf("%d ",a[i][j]);
}
}
4.就不能换个变量名吗?
考察点:
1.生存期与作用域的理解
2.传值与传址的区别
对应知识点:
1.详见上篇2022纳新
2.传值仅仅只是将主函数中实参的值赋给了自定义函数中型参的值。在自定义函数的被调用的过程中其中变量的改变对于主函数中实参的值并无任何影响(两者本身便为两个变量之间并无任何联系。)传址则是将主函数中作为参数的数的地址传递过去由自定义函数接收,因此自定义函数中的一系列操作便会影响存储在这两个位置上的值即主函数中作为参数的数。
补充:关于关键字static,详见C/C++ 中的static关键字 - 知乎
答案与原因见代码注释:
首先应明确一个大前提,代码中虽有多个变量名叫a与ver,但他们不是同一个变量!!!
#include<stdio.h>
int ver=123;
void func1(int ver)//将主函数中的ver的值赋给该参数
{
ver++;
printf("ver=%d\n",ver);//1026
}
void func2(int*pr)//传入主函数中ver的地址
{
*pr=1234;//在此函数内部修改pr指向的地址中的值
printf("*pr=%d\n",*pr);//1234
pr=5678;//error(将int类型常量赋给了地址)
printf("ver=%d\n",ver);//123,该处ver为定义在函数外部的全局变量,只有全局变量能在程序的任意位置访问
}
int main()
{
int a=0;//定义在函数内部,仅在主函数内可访问
int ver=1025;
for(int a=3;a<4;a++)
{
static int a=5;//静态全局变量(块作用域)
printf("a=%d\n",a);//5
a=ver;
printf("a=%d\n",a);//1025
func1(ver);
int ver=7;//块作用域
printf("ver=%d\n",ver);//7
func2(&ver);
}
printf("a=%d\tver=%d\n",a,ver);//a为0,定义在for循环中的a已经随着循环的结束而消亡。ver为1025,在函数内部,定义的重名变量会覆盖全局变量,且在func1中,主函数中ver的值并未被改变。
return 0;
}
6.算不对的算术
void func(void)
{
short a = -2;
unsigned int b = 1;
b += a;
int c = -1;
unsigned short d = c * 256;
c <<= 4;
int e = 2;
e = ~e | 6;
d = (d & 0xff) + 0x2022;
printf("a=0x%hx\tb=0x%x\td=0x%hx\te=0x%x\n", a, b, d, e);
//a=0xfffe,b=0xffffffff,d=0x2022,e=0xffffffff
printf("c=Ox%hhx\t\n", (signed char)c);
//c=0xf0
}
考察点:位运算
对应知识点:详见上篇2022纳新题
详细计算过程便不在此赘述
7.指针与数组的恩怨情仇
考察点:对于指针及数组之间关系的理解
对应知识点:定义一指针指向数组中的某一元素,可通过指针修改存放在该位置上的值。通过指针的自增与自减即可实现指针在该连续空间上的移动,从而达成遍历数组的目的。
答案:10 4 9
#include<stdio.h>
include<stdio.h>
int main()
{
int a[3][3]={
{1,2,3},{4,5,6},{7,8,9}};
int (*b)[3]=a;//指向数组的指针
++b;//b+1即一维数组上向后移动一个存储单元至{4,5,6}这一行,并指向该单元第一个位置,此时数组b的首地址为a[1][0]的地址
b[1][1]=10;//此时b[1][1]即为a[2][1],该步将a[2][1]的值修改为10
int*ptr=(int*)(&a+1);//&a指向a数组开头,+1后指向该数组存储位置结束时的下一个位置,该位置为b[3][0]
printf("%d %d %d\n",a[2][1],**(a+1),*(ptr-1));
/*
a[2][1]为10,
**(a+1)表示为:数组a首先移动一个int[3]到{4,5,6}一行指向该行第一个元素4,解两次引用即可得到该位置上存放的元素4
*(ptr-1)指b数组的后的一个位置减去一个元素地址得到上一个数组的最后一个元素9
*/
return 0;//结果为10 4 9
8.移形换位之术
考察点:指针与const之间的关系
知识点:详见上篇2022纳新
改错:
void func2(const int *n)//被指针指向的值不可修改
{
*n+=1;//不能向只读参数赋值
n=&a;//dui
}
void func3(int*const n)//指针指向的位置不可修改
{
*n+=1;//dui
n=&a;//不能向只读位置赋值
}
void func4(const int *const n)
{
*n+=1;//不能向只读参数赋值
n=&a;//不能向只读位置赋值
}
9.听说翻转字母大小写不影响英文阅读
考察点:
1.对于ascii码的应用
2.自定义函数的代码实现
char *convert(const char *s)
{
int i;
char *p=(char*)malloc(sizeof(char)*strlen(s));
//动态分配空间
p[21]='\0';//增加终止符
strcat(p,s);//拼接
for(i=0;i<21;i++){
if(p[i]>='A'&&p[i]<='Z'){//利用ascii码完成对于大小写的转换
p[i]=p[i]+'a'-'A';
}else if(p[i]>='a'&&p[i]<='z'){
p[i]=p[i]-'a'+'A';
}
}
free(p);//释放空间
return p;
}
10.交换礼物的方式
#define Swap1(a, b, t)
do {
t = a;
a = b;
b = t;
} while (0)//正确
#define Swap2(a, b)
do {
int t = a;
a = b;
b = t;
} while (0)//正确
void Swap3(int a, int b)
{
int t = a;
a = b;
b = t;
}//错误,只进行了值传递
//形参在函数调用完之后自动释放空间,main函数里并不会发生交换
考察点:
1.宏的定义与展开
2.传值与传址的区别(同4)
答案:
1.swap3错误,在函数中对于变量的操作并不会对于主函数中的实参造成影响
2.配合宏来使用,用大括号将宏括起来防止发生错误,0使循环只进行一次
3.其他交换方式
思路1.用指针将两者的地址进行交换
void swap(int* p1,int* p2)
{
int temp=*p1;
*p1=*p2;
*p2=temp;
}
2.采用取异或的方式进行交换
11.据说有个东西叫参数
该题内容与2022纳新中有所重复,详见上篇2022纳新
代码实现对于argv的遍历:
int main(int argc,char *argv[])
{
int i=0;
printf("argc = %d\n",argc);
while(argv[i] != NUll)//当元素不为空时,进行下一轮循环输出数组argv
printf("%s\n",argv[i++]);
return 0;
}
12.人去楼空
int *func1(void)
{
static int n = 0;
n = 1;
return &n;
}
int *func2(void)
{
int *p = (int *)malloc(sizeof(int));
*p = 3;
return p;
}
int *func3(void)
{
int n = 4;
return &n;//注意:不能返回局部变量地址这是很危险的,可采用动态分配内存的方法来解决这一问题
}
int main(void)
{
*func1() = 4;
*func2() = 5;
*func3() = 6;
考察点:静态变量的应用
知识点:在程序离开定义静态变量的函数之后他们并不会立即消失
错误:函数1中定义了静态变量n,在离开函数1后该变量依然存在,不能再对其重新定义
13.奇怪的输出
int main(void)
{
int data[] = {0x636c6557, 0x20656d6f, 0x78206f74,0x756f7969, 0x6e694c20, 0x67207875,0x70756f72, 0x32303220, 0x00000a31};
puts((const char*)data);
}
考察点:大小端问题
输出:Welcome to xiyou linux group 2021
更多相关请见上篇2022年纳新题解
14.请谈谈从c语言文件到可执行文件的过程的理解
1.预处理:在此阶段会将头文件展开,完成对于宏的替换,条件编译的处理(#if #endif等),去掉注释部分将.c文件处理后变为.i临时文件
2.编译:词法分析,语义分析,语法分析,符号汇总,将对每个.i文件单独转化为单独的.s文件
3.汇编:形成符号表,将.s的汇编文件变成.o的目标文件
4.合并段表:合并符号和符号表的重定位,把每个目标文件.o合并为一个可执行程序(linux默认a.out文件)