目录
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);
//16 12
}
sizeof(s)读取总长度不会因为‘\0’而结束,s[]末尾应该有第四个‘\0’,第四个'\0'表示字符串的结束,strlen函数读取到‘\0’就会停止且不包含'\0',所以只考虑了字母和空格,最终打印结果是16 12
2.箱子的大小和装入物品的顺序有关
#include<stdio.h>
struct test1
{
int a;
short b;
double c;
};
struct test2
{
short b;
int a;
double c;
};
int main(void)
{
struct test1 t1;
struct test2 t2;
printf("sizeof(t1): %d\n", sizeof(t1));
//sizeof(t1): 16
//sizeof(t2): 16
printf("sizeof(t2): %d\n", sizeof(t2));
return 0;
}
涉及结构体的内存对齐原则,结构体中最大数据类型是double所以对齐数是8,总大小得是8的整数倍 所以第一个结构体的大小是4+2(->4)+8=16,第二个结构体的大小是2(->4)+4+8=16.
3.哦,又是函数
/*在这里补全func函数的定义*/
int main(void) {
int arr[10][13];
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 13; j++) {
arr[i][j] = rand();
}
}
func(arr);
}
考察动手写代码的能力
#include<stdio.h>
#include<stdlib.h>
void func(int arr[10][13])
{
for(int i = 0;i < 10; i++ )
{
for(int j = 0; j < 13; j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
}
int main(void)
{
int arr[10][13];
for(int i = 0;i < 10; i++ )
{
for(int j = 0; j < 13; j++)
{
arr[i][j] = rand()%5;
}
}
func(arr);
return 0;
}
4.就不能换个变量名吗?
#include<stdio.h>
int ver = 123;
void func1(int ver)
{
ver++;
printf("ver = %d\n",ver);
}
void func2(int *pr)
{
*pr = 1234;
printf("*pr = %d\n",*pr);
pr = 5678;
printf("ver = %d\n",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);
a = ver;
func1(ver);
int ver = 7;
printf("ver = %d\n",ver);
func2(&ver);
}
printf("a = %d\tver = %d\n", a, ver);
return 0;
}
- 传值:在函数调用时,将
实际参数的值
复制给形式参数,函数内部对形式参数的修改不会影响到实际参数的值
。在函数中对形式参数的修改只是对其副本的修改,不会影响到原始数据. - 传址:在函数调用时,将
实际参数的地址传递
,函数内部通过指针的形式访问实际参数。在函数中修改实际参数。 - 生命周期指的
是变量在程序运行过程中有效的时间段
#include<stdio.h>
int ver = 123;//全局变量整个代码有效
void func1(int ver)
{
ver++;//1025++
printf("ver = %d\n",ver);
//ver = 1026
}
void func2(int *pr)//指针指向ver
{
*pr = 1234;
//对所指区域的赋值
printf("*pr = %d\n",*pr);
//*pr = 1234
pr = 5678;//改变地址
printf("ver = %d\n",ver);
//全局变量ver = 123
}
int main()
{
int a = 0;
int ver = 1025;//重定义,作用域为main函数
for(int a = 3; a < 4; a++)//对a的重定义出了for循环就销毁
{
static int a = 5;//静态,使a的生命周期延长,出循环也有效
printf("a = %d\n", a);
//a = 5
a = ver;//此处er是main函数的1025
func1(ver);//从此处开始看函数
int ver = 7;//重定义,本作用域有效
printf("ver = %d\n",ver);
//ver = 7
func2(&ver); //传er的地址
}
printf("a = %d\tver = %d\n", a, ver);
//a = 0 ver = 1025对应main函数的int
return 0;
}
5.套娃真好玩
#include<stdio.h>
unsigned sum(unsigned n)
{
return n ? sum(n-1) + n : 0 ;
}
int main(void)
{
printf("%u\n", sum(100));
return 0;
}
解析在下方代码注释,最终结果是5050.
#include<stdio.h>
unsigned sum(unsigned n)
{
return n ? sum(n-1) + n : 0 ;
//a ? b : c;是一个语句判断a是否为真,为真则输出b,否则输出c
//当n!=0的时候会连续的将n-1传入此函数直到n=0
//最终结果就是从100+到0为5050
}
int main(void)
{
printf("%u\n", sum(100));
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);
printf("c=Ox%hhx\t\n", (signed char)c);
}
解析在注释,本题考察进制转换。
#include<stdio.h>
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\t\n", a, b);
//a=0xfffe b=0xffffffff
//无符号整形最大值4294967295最小值是0,小于0之后会变成最大值转换为16进制是ffffffff
printf("d=0x%hx\te=0x%x\n",d,e);
//d=0x2022 e=0xffffffff
//将负数短整形转换为无符号短整形并转化为16进制输出
printf("c=0x%hhx\t\n",(signed char)c);
//c=0xf0
}
int main()
{
func();
return 0;
}
7.指针和数组的恩怨情仇
int main(void) {
int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int(*b)[3] = a;
++b;
b[1][1] = 10;
int *ptr = (int *)(&a + 1);
printf("%d %d %d \n", a[2][1], **(a + 1), *(ptr - 1));
}
解析写在代码里。
#include<stdio.h>
int main(void)
{
int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int(*b)[3] = a;
++b;
b[1][1] = 10;
int *ptr = (int *)(&a + 1);
printf("%d %d %d \n", a[2][1], **(a + 1), *(ptr - 1));
// 10 4 9
//b先指向了二维数组的第一行b++之后换到了第二行然后对b[1][1]赋值等于对a[2][1]赋值
//相当于先到二维数组的第二行然后直接输出第一个是4
//ptr的地址相当于在整个二维数组的后一位,-1之后回到了二维数组的最后一位
return 0;
}
8.移形换位之术
int a = 1;
int const b = 2;
const int c = 3;
void funco(int n) {
n += 1;
n = a;
}
void func1(int *n) {
*n += 1;
n = &a;
}
void func2(const int *n) {
*n += 1;
n = &a;
}
void func3(int *const n) {
*n += 1;
n = &a;
}
void func4(const int *const n) {
*n += 1;
n = &a;
}
- fun2中的*n为const不可被改变,所以*n+=1是错误的。
- func3中的*n的地址不可以被改变,所以n=&a是错误的。
- fun4中的*n的值和地址都不可以被改变所以*n+=1与n=&a都是错误的。
const int与int const等价都表示一个不可以被改变的整型
int *const与const int *两个语句因为*与const的先后不一,*在前表示地址不可以被改变,const在前表示指针所指向的数值不可被改变
9.听说翻转字母大小写不影响英文的阅读
char *convert(const char *s);
int main(void) {
char *str = "XiyouLinux Group 2022";
char *temp = convert(str);
puts(temp);
}
考察打代码的实际能力 与上一篇22年第9题手脑并用相似,直接上代码。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
char *convert(const char *s)
{
int i,len=strlen(s);//求字符串长度
char *RESULT=(char*)malloc((len+1)*sizeof(char));
//申请内存
strcpy(RESULT,s);//拷贝字符串
for(i=0;i<len;i++)
{
if(RESULT[i]>='a'&& RESULT[i]<='z') RESULT[i]-=32;
else if(RESULT[i]>='A'&& RESULT[i]<='Z') RESULT[i]+=32;
//转换大小写
}
return RESULT;
}
int main(void)
{
char *str = "XiyouLinux Group 2022";
char *temp = convert(str);
puts(temp);
free(temp);
return 0;
}
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;
}
- swap1是正确的使用了一个变量t来交换a和b,只不过t后面要使用的话,会使t的值改变;
- swap2是正确的,在do while中定义了一个变量用来交换a和b的值,出了do while t即会销毁。
- swap3是错误的,它是一个普通函数,传入了a和b的值,在函数内部进行交换,这个交换出了函数并不会存在,不会对函数外面的a和b有改变。
- do{}while(0)使得语句是一个整体,防止定义宏多个语句的时候出现问题。
- 还有异或和使用指针完成交换
#define Swap4(a, b) do { a = a ^ b; b = a ^ b; a = a ^ b; }while(0) void Swap5(int *a,int *b) { int t=*a; *a=*b; *b=t; }
11. 据说有个东西叫参数
int main(int argc, char *argv[])
{
printf("argc = %d\n", argc);
for (int i = 0; i < argc; i++)
printf("%s\n", argv[i]);
}
- argc表示传递给程序命令行参数的数量,至少是1,第一个参数始终是程序的名称。
- argv是一个指向字符串数组的指针,每个字符串表示一个命令行参数。
#include<stdio.h>
int main(int argc, char *argv[])
{
printf("argc = %d\n", argc);
for (int i = 0; argv[i]!='\0'; i++)//字符串肯定以'\0'结尾
//当读到‘\0’时候停止循环
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;
}
代码在函数*func3里面有一处错误,指针指向了局部变量,在函数的调用结束时候n的生命周期结束,会导致指向它的指针成为悬空指针,访问这个指针会产生未定义的行为。
静态变量与其它变量之间的相同点
- 静态变量和其他变量都是用来存储数据的内存空间
- 静态变量和其他变量都可以被赋值和读取
不同点
- 静态变量在程序运行期间只被初始化一次,而其他变量每次进入作用域都会被初始化
- 静态变量的生命周期从其定义的位置开始,直到程序结束,而其他变量的生命周期在进入和离开其作用域时开始和结束
- 静态变量在函数内部定义时,会在函数调用结束后仍然保留其值,而其他变量在函数调用结束后会被销毁
13.奇怪的输出
int main(void) {
int data[] = {0x636c6557, 0x20656d6f, 0x78206f74,
0x756f7969, 0x6e694c20, 0x67207875,
0x70756f72, 0x32303220, 0x00000a31};
puts((const char*)data);
}
本题与大小端的存储方式,进制之间的转换有关,目前大多数电脑都是小端,数据存储的方式是从后往前,十六进制与二进制的转换是一位变四位,所以两个十六进制就是一个字节,从后往前依次读取0x57,0x65...转换为十进制对应ASCII表输出字符得到打印结果。
#include<stdio.h>
int main(void)
{
int data[] = {0x636c6557, 0x20656d6f, 0x78206f74,
0x756f7969, 0x6e694c20, 0x67207875,
0x70756f72, 0x32303220, 0x00000a31};
puts((const char*)data);
//Welcome to xiyou Linux group 2021
return 0;
}
14.请谈谈对从「C语言文件到可执行文件」的过程的理解
C语言文件到可执行文件的过程主要包括以下几个步骤:
- 预处理:预处理器对源代码文件中的宏(#开头)进行替换,生成一个中间代码文件。
- 编译:编译器将中间代码翻译成目标代码(汇编语言或机器代码)。
- 汇编:汇编器将目标代码翻译成汇编语言。
- 链接:链接器将汇编语言与所需的库函数和目标文件连接起来,生成一个可执行文件。
15. (选做) 堆和栈
堆和栈是计算机内存中两个重要的数据结构,具有不同的特性和用途。以下是堆和栈之间的主要区别:
- 存储方式:堆是动态分配的内存区域,可以通过malloc函数进行分配;而栈是自动分配的内存区域,由系统自动释放 。
- 申请方式:栈是由系统自动分配,例如在声明函数的一个局部变量时,系统自动在栈中为该变量开辟空间;而堆需要程序员自己申请,并指明大小,在C中用malloc函数,在C++中用new运算符。
- 申请后系统的响应:只要栈的剩余空间大于所申请的空间,系统将为程序提供内存,否则将报异常提示栈溢出;而堆操作系统有一个记录空间内存地址的链表,当系统收到程序的申请时,会遍历链表,寻找第一个空间大于所申请空间的堆节点,然后将节点从内存空闲节点链表中删除,并将该节点的空间分配给程序。
- 生命周期:栈区的数据生命周期由编译器自动管理,一般在函数调用时创建,函数返回时销毁;堆区的数据则需要程序员手动管理,通过调用free函数来释放内存。
16.(选做) 多文件
在C语言中,通常我们通过.h文件来调用另一个文件中的函数。具体来说,首先在一个.c源文件中对函数进行定义,然后在.h头文件中进行声明。这样,当需要在其他.c文件中调用这个已经定义好的函数时,只需要导入声明该函数的.h文件即可。
#include<要调用的函数文件头>
但是,如果您不想使用头文件,也可以直接在需要调用函数的文件中对需要的函数进行声明。
//在某个.c文件中直接对函数进行声明
int sum(int a, int b);
然后在之后的代码中就可以直接调用这个函数了。不过需要注意的是,这种做法只推荐在函数较为简单、调用处集中的情况下使用。
17. (选做)
GNU/Linux
与文件
- 创建文件:
touch filename
-
创建文件夹:
mkdir foldername
-
在GNU/Linux下,
ls
命令的每一列的含义如下:
-
第一列:文件/文件夹的权限。
- 第二列:硬链接数目。
- 第三列:文件/文件夹的所有者。
- 第四列:文件/文件夹的所属组。
- 第五列:文件/文件夹的大小(以字节为单位)
- 第六列:最后修改的日期和时间
- 第七列:文件/文件夹的名称