2021西邮linux兴趣小组纳新题题解

1. 大小和长度竟然不是一个意思

sizeof()strlen()有什么异同之处?

他们对于不同参数的结果有什么不同?请试举例子说明。

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会统计字符串结尾的\0;而strlen不会,它只统计\0之前的字符串个数,遇到\0停止
  • 因此sizeof的结果比strlen的多能看见的三个\0,以及在字符串末尾自动加上的再一个\0,即一共多四个\0。So,16和12相差4
举个栗子ヾ(•ω•`)o
int main(){
    char arr1[]="abc";
    char arr2[]={'a','b','c'};
    printf("%d\n"sizeof(arr1));
    printf("%d\n"sizeof(arr2));
    printf("%d\n"strlen(arr1));
    printf("%d\n"strlen(arr2));
}
  • 根据以上的说明,可知该程序的输出为4 3 3 26(随机值)(由于arr2没有结尾的\0,所以计算完‘c’后,继续往后计算,直到遇见随机的\0)
  • 嗯哼,没看够?点这里

注意:

  • sizeof是运算符,计算对应类型所占的空间数,不是函数;而strlen是在#include<string.h>头文件下的一个计算字符串长度的函数,且只能(char*)字符串作为参数

2. 箱子的大小和装入物品的顺序有关

test1test2都含有:1个short、1个int、1个double,那么sizeof(t1)sizeof(t2)是否相等呢?这是为什么呢?

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));
  printf("sizeof(t2): %d\n", sizeof(t2));
}
  • 相等,均为16
  • 了解一下结构体的内部成员存放和结构体的大小:
    1.结构体内部的成员不是连续紧挨着存放的
    2.结构体可以分成N份,每份的大小是最大成员类型的长度
    3.结构体的大小为N * 最大成员类型的长度
再看这题:
  • 举该题t1的例子,由于double是占了8个字节,为结构体成员中的最大长度,因此每份大小为8
  • 第一份中,int占了4个字节,还剩8-4=4个字节,∵4>2(shout)接下来的short可以继续放在第一份中,此时第一份里只剩下8-4-2=2个字节,∵2<8(double)double无法再放入第一份,得重开新的第二份;
  • 第二份刚好放下double;
  • 在这个结构体中一共用了两份,所以结构体大小为8(每份大小)× 2(份数)=16

3. 哦,又是函数

想必在高数老师的教导下大家十分熟悉函数这个概念。那么你了解计算机程序设计中的函数吗?请编写一个func函数,用来输出二维数组arr中每个元素的值。

/*在这里补全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);
}
  • 上代码
void fun( int a[][13] ){
	int i ,j;
	for( i = 0 ; i < 10 ; i++ ){
		for( j = 0 ; j < 13 ; j++ ){
			printf( "%d" , a[i][j] );
		}
	}
}
  • over

4.就不能换个变量名吗?

  • 请结合下面的程序,简要谈谈传值传址的区别。
  • 简要谈谈你对C语言中变量的生命周期的认识。
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);
}
  • 看结果:
    在这里插入图片描述

  • 传值:把A的数值传到B,改变B,A不会跟着变,B存的是跟A一样的值

  • 传址:把A的地址传到B,改变B,A同时跟着变,B存的是A的地址

  • 生命周期:将一对大括号称为“块”
    1.本地变量定义在块内,且不会被默认初始化(因此第一个的输出结果为随机值)
    2.程序运行到“块”之前,其中的变量不存在;离开“块”,其中的变量消失了
    3.“块”外面定义的变量其一下到处有效
    4.“块”里面定义了和外面同名的变量,则会掩盖外面的

  • 据以上信息,便可得其结果


5. 套娃真好玩!

请说明下面的程序是如何完成求和的?

unsigned sum(unsigned n) { return n ? sum(n - 1) + n : 0; }
int main(void) { printf("%u\n", sum(100)); }
  • 结果为5050=100+99+…+1

  • 本题了解条件运算符的执行方法就能解决


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);
}

又双叒叕是位运算

在这里插入图片描述
注:图片来自位运算全面总结,关于位运算看这篇就够了

  • %hx 代表以16进制输出short类型的整数,因此a,b显而易见,不解释
  • 关于d,d为无符号的short,c*256=-256,所以此时d=216+(-256);接着d和0xff进行与运算,结果为0,再加上0x2022,所以d最终=0x2022
  • 关于e,先进行 ~(取反),取反结果和6进行或运算,结果为0xffffffff
  • 关于c,将c左移4位后,并强制类型转换成signed char,得0xfff0

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));
}
  • 结果:10 4 9
int main(void) {
 int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
 int(*b)[3] = a;//b和*结合,构成一个名为b指针,int修饰数组的内容(数组的每个元素),因此b指针指向该数组的首地址,
 //而这个数组有3个单元,每个单元都是int类型,且该数组没有名字>>指向含有3个int数组的指针
 //将a赋值给他,使b指针指向a数组第一个单元{1,2,3}中的首元素1
 ++b;//指针b指向a数组的第二个单元{4,5,6}中的首元素4
 b[1][1] = 10;//经过++b后,b[0][0]为4,所以这里的b[1][1]为8,并将其赋值为10(即a[2][1]=10)
 int *ptr = (int *)(&a + 1);//(&a+1)是指针ptr指向a数组的下一个数组(在a数组最后一个元素的后面)
 printf("%d %d %d \n", a[2][1], **(a + 1), *(ptr - 1));
}
  • a[2][1]据分析可得为10
  • **(a + 1)可理解为 * (*(a+1)+0)=a[1][0]=4
  • *(ptr - 1))指整个数组的下一个数组减去一个元素地址,得到上一个数组a的最后一个元素9


8. 移形换位之术

下面有abc三个变量和4个相似的函数。

  • 你能说出使用这三个变量的值或地址作为参数分别调用这5个函数,在语法上是否正确吗?
  • 请找出下面的代码中的错误。
  • const intint const是否有区别?如果有区别,请谈谈他们的区别。
  • const int *int const *是否有区别?如果有区别,请谈谈他们的区别。
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;
}

void func2(const int *n) {//const修饰指针*n,表示*n指向的值不能被改变
  *n += 1;//因此这里是错误的
  n = &a;
}
void func3(int *const n) {//const修饰地址,表示指针的地址不能被改变
  *n += 1;
  n = &a;//因此这里是错误的
}
void func4(const int *const n) {//const修饰指针和地址,两者均不能改变
  *n += 1;//因此这里是错误的
  n = &a;//因此这里是错误的
}
  • const int和 int const都表示int类型的变量不能被修改
  • const int *aint const *a:(*和a连在一起)表示指针a指向一个const int,即 *a的值为const,不能被修改
  • 而对于int *const a:(* 和a分开来了)表示地址固定的指针a不能被修改地址,但能修改*a的值
  • 更多见:here here

9. 听说翻转字母大小写不影响英文的阅读?

请编写convert函数用来将作为参数的字符串中的大写字母转换为小写字母,将小写字母转换为大写字母。返回转换完成得到的新字符串。

char *convert(const char *s);
int main(void) {
  char *str = "XiyouLinux Group 2022";
  char *temp = convert(str);
  puts(temp);
}
  • 上代码
char *convert(const char *s)
{
 	int len=strlen(s);
 	char *a=(char*)malloc(len);
 	strcpy(a,s);
 	char *q=a;
 	for(;*a!='\0';a++){
  		if(*a>='a'&&*a <= 'z') 
		  *a -= 32;
  		else if(*a >= 'A'&&*a <= 'Z') 
		  *a += 32;
 	}
	 return q;
}

10. 交换礼物的方式

  • 请判断下面的三种Swap的正误,分别分析他们的优缺点。
  • 你知道这里的do {...} while(0)的作用吗?
  • 你还有其他的方式实现Swap功能吗?
#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、2正确,swap3错误
  • swap1:直接从原函数中传递参数,方便快捷
  • swap2:在参数中创建交换变量t,作用时间短,且占用内存空间小
  • swap3:只传递了a,b变量的值而并没有传递地址,所以仅在函数内部改变了a,b的值而函数作用完后就释放掉函数中变量的内存,a,b的值还是没有改变
  • do {…} while(0)的作用实现局部作用域(为了在宏定义中使用多个语句块而不会受大括号和分号的影响)
  • 用指针啦
    在这里插入图片描述

11. 据说有个东西叫参数

你知道argcargv的含义吗?请解释下面的程序。你能在不使用argc的前提下,完成对argv的遍历吗?

int main(int argc, char *argv[]) {
  printf("argc = %d\n", argc);
  for (int i = 0; i < argc; i++)
    printf("%s\n", argv[i]);
}
  • argc:外部输入的参数个数
  • argv:参数的字符串数组,用来存放指向字符串参数的指针数组
  • (argc=1时,表示只有一个程序名称,存储在argv[0]中;argv[0]指向程序运行的全部路径名;argv[1]指向程序在DOS命令中执行程序名的第一个字符串,后面以此类推)
  • 见代码
int main(int argc, char *argv[]) {
  printf("argc = %d\n", argc);
  for ( int i = 0; argv[i]!='\0'; i++)
    printf("%s\n", argv[i]);
}

一样的结果:
在这里插入图片描述


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;
}
  • 解答见下
int *func3(void) {
  int n = 4;
  return &n;//不可返回局部变量的地址(&n),作用完就被释放了
}

13. 奇怪的输出

int main(void) {
  int data[] = {0x636c6557, 0x20656d6f, 0x78206f74,
                0x756f7969, 0x6e694c20, 0x67207875,
                0x70756f72, 0x32303220, 0x00000a31};
  puts((const char*)data);
}
  • 这里涉及到一个知识点:大端法,小端法
  • 在了解这个之前,先了解一个专业词:字节序。字节序,顾名思义就是字节的排列顺序,而计算机中既可以从高位到低位进行排列,也能从低位到高位进行排列
  • 现在再来看大端法、小端法:假设有数据0x12345678,左边为高字节,右边为低字节。将高字节的数据放在低地址,便是大端法;反之,为小端法

  • 下图,上面一排为字节的存放地址,从左到右,地址由低到高;下面一排为字节,分别存放在不同的地址中
  • 大端法
    在这里插入图片描述
  • 小端法
    在这里插入图片描述

  • 一般家庭计算机所使用的为小端法,因此根据以上知识并查阅阿斯克码表可得,该程序的输出为Welcome to xiyou Linux group 2021

14. 请谈谈对从「C语言文件到可执行文件」的过程的理解

在这里插入图片描述


全题终。。。。。。

点个赞再走呗ヾ(•ω•`)o

2022西邮linux兴趣小组纳新题题解

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值