0.鼠鼠我啊,要被祸害了
有 1000 瓶水,其中有一瓶有毒,小白鼠只要尝一点带毒的水,24 小时后就会准时死亡。至少要多少只小白鼠才能在 24 小时内鉴别出哪瓶水有毒?
可以尝试用二进制数表示某一瓶是否有毒,第i位为1表示第i瓶水有毒,为0表示没毒,某瓶水对应的二进制编码(0或1)代表这瓶水要不要给第i只小白鼠喝(0不喝1喝)若喝了之后小白鼠死了则其对应位置为1,没死则为0,喂水之后根据几只小白鼠的生死状况会得到一个二进制编码,将其转化为10进制数字就可以看出哪瓶有毒,由2n>=1000 解得 至少需要10只小白鼠。
例如:若编码为1000100011的水有毒,则第1,2,6,10只小鼠死亡。
1.先预测一下~
按照函数要求输入自己的姓名试试~
char *welcome(){
//请你返回自己的姓名
}
int main(void){
char *a=welcome();
printf("Hi,我相信 %s 可以面试成功!\n",a);
return 0;
}
#include <stdio.h>
char *welcome()
{
char*name="XXX";\\XXX表示自己的名字
return name;
}
int main(void)
{
char *a=welcome();
printf("Hi,我相信 %s 可以面试成功!\n",a);
return 0;
}
2.欢迎来到 Linux 兴趣小组
有趣的输出,为什么会这样子呢~
int main(void) {
char *ptr0 = "Welcome to Xiyou Linux!";
char ptr1[] = "Welcome to Xiyou Linux!";
if (*ptr0 == *ptr1) {
printf("%d\n", printf("Hello, Linux Group - 2%d", printf("")));
}
int diff = ptr0 - ptr1;
printf("Pointer Difference: %d\n", diff);
}
输出结果:Hello,Linux Group - 2023
Pointer Difference:-318856704(随机值)
除了&数组名和sizeof(数组名)之外,其他数组名都表示首元素地址
指针的本质是地址
- *ptr0和ptr1都指向首元素,首元素相同,执行if语句,printf返回写入的字符总数,内层printf输出空字符串并返回0,外层printf打印字符串长度23,最终打印2023。
- 地址减地址返回地址差值。
3.一切都翻倍了吗
① 请尝试解释一下程序的输出。
② 请谈谈对 sizeof()和 strlen()的理解吧。
③ 什么是 sprintf(),它的参数以及返回值又是什么呢?
int main(void) {
char arr[] = {'L', 'i', 'n', 'u', 'x', '\0', '!'}, str[20];
short num = 520;
int num2 = 1314;
printf("%zu\t%zu\t%zu\n", sizeof(*&arr), sizeof(arr + 0),
sizeof(num = num2 + 4));
printf("%d\n", sprintf(str, "0x%x", num) == num);
printf("%zu\t%zu\n", strlen(&str[0] + 1), strlen(arr + 0));
}
输出结果:7 8 2 0 4 5
-
*&arr找到整个数组,计算数组长度,为7;
arr+0为首元素地址,64位机器下大小为8;
sizeof内部语句不执行,只注重变量类型,num是short类型,占两个字节;
%x将num格式化为16进制,打印0x208,比较结果不相等,返回0;
strlen计算长度,&str[0]+1指向第二个元素,后面字符总数为4;
arr+0是首元素地址,后面字符总数为5。
-
sizeof是一个操作符,计算变量所占内存空间的大小,单位是字节,要算上\0;
strlen是库函数,计算\0之前出现的字符个数,不算\0。 -
sprintf()是一个字符串格式化命令,主要用于将格式化的数据写入字符串中,参数包括指向字符串缓冲区的指针、包含格式字符串的c字符串以及可选参数,它会返回写入的字符总数。
4.奇怪的输出
程序的输出结果是什么?解释一下为什么出现该结果吧~
int main(void) {
char a = 64 & 127;
char b = 64 ^ 127;
char c = -64 >> 6;
char ch = a + b - c;
printf("a = %d b = %d c = %d\n", a, b, c);
printf("ch = %d\n", ch);
}
输出结果:a=64 b=63 c=-1 ch=-128
64&127结果为64,a=64(&:有0取0,没0取1)
64^127结果为63,b=63( ^:相同为0,不同为1)
将-64右移6位得到-1,c=-1
a+b=128,而char类型取值范围为-128~127,a+b会溢出,回到最小值-128。
5.乍一看就不想看的函数
“人们常说互联网凛冬已至,要提高自己的竞争力,可我怎么卷都卷不过别人,只好用一些奇技淫
巧让我的代码变得高深莫测。”
这个 func()函数的功能是什么?是如何实现的?
int func(int a, int b) {
if (!a) return b;
return func((a & b) << 1, a ^ b);
}
int main(void) {
int a = 4, b = 9, c = -7;
printf("%d\n", func(a, func(b, c)));
}
输出结果:6
本质:加法运算
func函数内用了函数递归,a^b会得到不考虑进位的运算结果,(a&b)<<1得到进位的值,一起使用相当于两数相加。
6.自定义过滤
请实现 filter()函数:过滤满足条件的数组元素。
提示:使用函数指针作为函数参数并且你需要为新数组分配空间。
typedef int (*Predicate)(int);
int *filter(int *array, int length, Predicate predicate,
int *resultLength); /*补全函数*/
int isPositive(int num) { return num > 0; }
int main(void) {
int array[] = {-3, -2, -1, 0, 1, 2, 3, 4, 5, 6};
int length = sizeof(array) / sizeof(array[0]);
int resultLength;
int *filteredNumbers = filter(array, length, isPositive,
&resultLength);
for (int i = 0; i < resultLength; i++) {
printf("%d ", filteredNumbers[i]);
}
printf("\n");
free(filteredNumbers);
return 0;
}
需要先为数组分配空间,然后遍历数组,找到满足条件的数,补全部分代码如下:
{
int *result=(int*)malloc(length*sizeof(int));
int count=0;
for(int i=0;i<length;i++)
{
if(predicate(array[i]))
{
result[count++]=array[i];
}
}
*resultLength=count;
return result;
}
7.静…态…
① 如何理解关键字 static?
② static 与变量结合后有什么作用?
③ static 与函数结合后有什么作用?
④ static 与指针结合后有什么作用?
⑤ static 如何影响内存分配?
(1)static意为静态的,被static修饰时,外部链接属性变为内部链接属性,可以限制变量作用域,实现“记忆”功能,修改变量的存储位置和生命周期,使其能够在程序运行期间始终存在,并能够被所有类实例共享。
(2)与变量结合
:生命周期延长:静态变量会在程序运行期间一直存在,而不仅仅是它们被声明的那个作用域。这意味着静态变量可以在函数调用之间保留它们的值,而不会在函数执行完毕后被销毁。
独立作用域:静态变量只属于它们被声明的文件或作用域,而不会在其他文件中被共享。这使得静态变量可以在不同函数或类之间提供一些隔离。
默认初始化:静态变量在第一次使用前会被自动初始化,通常为零或者 null。
(3)与函数结合
:会使变量具有静态存储期,这意味着该变量在程序执行期间一直存在,而不仅仅是在函数调用期间存在。当函数执行完毕后,静态变量的值会保留下来,可以在下次函数调用时使用。
(4)与指针结合
:保持指针的指向:当一个指针指向一个静态变量时,即使程序执行过程中有多个函数调用,该指针始终指向同一个静态变量。这是因为静态变量在程序执行期间一直存在,而动态分配的内存则在程序执行期间会被释放。隐藏指针的外部可见性:当一个指针被声明为静态时,它只能在声明它的文件内可见,而在其他文件中无法访问。这可以用来限制对指针的访问权限,从而保护数据的安全性。
8.救命!指针!
数组指针是什么?指针数组是什么?函数指针呢?用自己的话说出来更好哦,下面数据类
型的含义都是什么呢?
int (p)[10];
const int p[10];
int (f1(int))(int, int);
- 指针数组:用来存放指针的数组;
- 数组指针:指向数组的指针,用来存放数组的地址;
- 函数指针:指向函数的指针,
- int (*p)[10]是数组指针,p先与解引用操作符结合,说明其本质是指针;
- const int* p[10]是指针数组,p先与[ ]结合,说明其本质是数组;
- int (f1(int))(int, int)是函数指针。
9.咋不循环了
程序直接运行,输出的内容是什么意思?
int main(int argc, char* argv[]) {
printf("[%d]\n", argc);
while (argc) {
++argc;
}
int i = -1, j = argc, k = 1;
i++ && j++ || k++;
printf("i = %d, j = %d, k = %d\n", i, j, k);
return EXIT_SUCCESS;
}
输出结果:0 1 2
argc命令行参数的个数
argv[0]指向程序名,argv字符指针数组用于保存命令行参数
题中while循环当argc为0(循环中argc的值过大,超出int的范围时,argc会变为-1,最后一直加一加到0)时停止,i为-1(真),所以i自增为0,j=0+1=1,k++结果为2.
10.到底是不是 TWO
#define CAL(a) a * a * a
#define MAGIC_CAL(a, b) CAL(a) + CAL(b)
int main(void) {
int nums = 1;
if(16 / CAL(2) == 2) {
printf("I'm TWO(ノ>ω<)ノ\n");
} else {
int nums = MAGIC_CAL(++nums, 2);
}
printf("%d\n", nums);
}
宏定义内容在预处理阶段由预处理器进行替换(会递归进行)宏定义最终在调用宏的地方把宏体原地展开
题中16/CAL(2)展开之后为16/222,结果不等于2,执行else部分
但接下来的语句(++nums*++nums*++nums)是未定义行为,应避免在一个表达式中多次进行自增操作,两次自增操作之间没有明确规定,不同编译器可能会有不同的行为,因此结果为初始赋值1。
11.克隆困境
试着运行一下程序,为什么会出现这样的结果?
直接将 s2 赋值给 s1 会出现哪些问题,应该如何解决?请写出相应代码。
struct Student{
char *name;
int age;
};
void initializeStudent(struct Student *student, const char *name,
int age) {
student->name = (char *)malloc(strlen(name) + 1);
strcpy(student->name, name);
student->age = age;
}
int main(void) {
struct Student s1, s2;
initializeStudent(&s1, "Tom", 18);
initializeStudent(&s2, "Jerry", 28);
s1 = s2;
printf("s1 的姓名: %s 年龄: %d\n", s1.name, s1.age);
printf("s2 的姓名: %s 年龄: %d\n", s2.name, s2.age);
free(s1.name);
free(s2.name);
return 0;
}
s1=s2创建了一个新的变量,并将其地址赋给s1,但新变量没有自己的name成员,只是指向s2.name所指向的内存,所以修改s1的name时,实际上也修改了s2.name所指向的内存,会导致重复释放,而原先内存没有释放,会导致内存泄漏。
使用指针代替结构体,就可以直接操作原始变量而不是它的副本。
修改后的代码如下:
struct Student{
char *name;
int age;
};
void initializeStudent(struct Student **student, const char *name,
int age) {
*student=(struct student*)malloc(sizeof(struct student));
(*student)->name = (char *)malloc(strlen(name) + 1);
strcpy((*student)->name, name);
(*student)->age = age;
}
int main(void) {
struct Student *s1, *s2;
initializeStudent(&s1, "Tom", 18);
initializeStudent(&s2, "Jerry", 28);
*s1 = *s2;
printf("s1 的姓名: %s 年龄: %d\n", s1->name, s1->age);
printf("s2 的姓名: %s 年龄: %d\n", s2->name, s2->age);
free(s1->name);
free(s2->name);
free(s1);
free(s2);
return 0;
}
12.你好,我是内存
作为一名合格的 C-Coder,一定对内存很敏感吧~来尝试理解这个程序吧!
struct structure {
int foo;
union {
int integer;
char string[11];
void *pointer;
} node;
short bar;
long long baz;
int array[7];
};
int main(void) {
int arr[] = {0x590ff23c, 0x2fbc5a4d, 0x636c6557, 0x20656d6f,
0x58206f74, 0x20545055, 0x6577202c, 0x6d6f636c,
0x6f742065, 0x79695820, 0x4c20756f, 0x78756e69,
0x6f724720, 0x5b207075, 0x33323032, 0x7825005d,
0x636c6557, 0x64fd6d1d};
printf("%s\n", ((struct structure *)arr)->node.string);
}
这道题首先定义了一个结构体变量,用来储存arr数组中的内容,后面将arr转换为指向structure结构体的指针并访问node.string成员。
小端存储是低字节存低地址,大端存储是高字节存低地址,一般计算机都是小端存储,因此内容反向输出,再根据对应的ASCII值得到最终结果"Welcome to XUPT,welcome to xiyou Linux Group [2023]"
13.GNU/Linux (选做)
注:嘿!你或许对 Linux 命令不是很熟悉,甚至你没听说过 Linux。但别担心,这是选做题,了解
Linux 是加分项,但不了解也不扣分哦!
你知道 cd 命令的用法与 / . ~ 这些符号的含义吗?
请问你还懂得哪些与 GNU/Linux 相关的知识呢~
- cd命令:改变当前的工作目录;
- /:要进行操作的目录;
- .:当前目录;
- ~:当前用户的主目录。