XiyouLinuxGroup2020补纳面试题

一.简单的错误

        请分析下⾯的程序,指出其中的问题

#include <string.h>
int main(void)
{
char *str = "linux";
str[0] = "L";
char a;
char *t = &a;
strcpy(t, str);
printf("%s\n", t);
}

        运行该程序,很显然会报错,一是没有print的头文件#include<stdio.h>,二是因为str[0].

        因为str是一个字符串指针,而字符串指针类似于const char str[],字符串指针指向的内容是不可修改的,用字符串指针定义的是存放在静态存储区,是常量,不可更改,想要将其更改正确,可以:

#include<stdio.h>
#include<string.h>
int main(){
    char str[] = "linux";//想要更改可以用数组方式定义
    char*p=str;//因为后面t为指针,所以复制也用指针。
    str[0]='L';
    char a;
    char *t=&a;
    strcpy(t,p);
    printf("%s\n",t);
}

二.传递多维数组参数        

        请将三维数组 arr 作为实参传递给 func() 并写出 func() 的函数定义

int main(void)
{
int l = 5, m = 10, n = 20;
int arr[l][m][n];
}

       这道题就是一个简单的数组传递,传递数组有值传递地址传递,值传递效率比较低。

#include<stdio.h>
#include<stdlib.h>
// void func(int arr[][10][20]){//必须知道一维和二维的数值//方法一
//     for(int i=0;i<5;i++){
//         for(int j=0;j<10;j++){
//             for(int k=0;k<20;k++){
//                 arr[i][j][k]=rand()%100+1;
//             }
//         }
//     }
// }
void func(int (*arr)[10][20]){//方法二
    for(int i=0;i<5;i++){
        for(int j=0;j<10;j++){
            for(int k=0;k<20;k++){
                arr[i][j][k]=rand()%100+1;
            }
        }
    }
}
// void func(int l,int m,int n,int arr[l][m][n]){//变长数组的传递方式
//     for(int i=0;i<l;i++){
//         for(int j=0;j<m;j++){
//             for(int k=0;k<n;k++){
//                 arr[i][j][k]=rand()%100+1;
//             }
//         }
//     }
// }

// void print(int arr[][10][20]){//输出方式一
//     for(int i=0;i<5;i++){
//         for(int j=0;j<10;j++){
//             for(int k=0;k<20;k++){
//                 printf("%d ",arr[i][j][k]);
//             }
//             printf("\n");
//         }
//         printf("\n");
//     }
// }

void print(int (*arr)[10][20]){//输出方式二
    for(int i=0;i<5;i++){
        for(int j=0;j<10;j++){
            for(int k=0;k<20;k++){
                //printf("%d ",arr[i][j][k]);
                printf("%d ",*(*(*(arr+i)+j)+k));
            }
            printf("\n");
        }
        printf("\n");
    }
}

int main(){
    int l=5,m=10,n=20;
    int arr[l][m][n];
    //func(l,m,n,arr);//变长输出的调用
    func(arr);
    print(arr);

    return 0;
}

三.纷乱的指针

int arr[10][5][3];
void *pr1 = (void *)arr;
int *pr2 = (int *)arr;
long *pr3 = arr;
char *pr4 = (char *)arr;
int *t[4];
t[0]=(int *)&arr[5];
t[1]=(int *)&arr[1][1][0];
t[2]=t[0];
t[3]=(int *)&t[0];

将 arr 的值记为: A

将 t 的值记为: B

将 char 类型的变量⼤⼩记为 1

int ⼤⼩为 4 , long ⼤⼩为 8

本题含⼀处语法错误,请找出。

请以 A+n 的形式说明以下内容的值:

&arr[0][0][0]
&arr[0][0][1]
&arr[0][1][0]
&arr[1][0][0]
&arr[1][0][1]
&arr[0][1][1]
arr
**arr+1
*(*arr+1)
**(arr+1)
*(*arr+1)+1
*(*(arr+1))+1
&arr[1][0][0]+1
&arr[0][2]+1
&arr[1]+1
&arr+1
pr1+2
pr2+2
pr3+2
pr4+2

请以 A+n 或 B+n 的形式说明下列值:

t
t+1
t[0]+1
&t[0]+1
*(&t[0]+1)+1
t[1]
(int(*)[4])t[1]
((int(*)[4])t[1])+1
*((int(*)[3][4])t[2]+1)+1
t[3]
t[3]-1

        解答

        先对题目进行分析:

​
int arr[10][5][3]; 
	void *pr1 = (void *)arr;//将三维数组变成一维,void*输出时需要强制类型转换为int*
	int *pr2 = (int *)arr;//将三维数组变成一维
	long *pr3 = arr;
    //将int数组变化为long型数组,当用指针调用时,比如*(pr3+1)的+1地址为long的,是int的二倍,所以会跳过一个数。
    //用pr3遍历会数组大小减半;
	char *pr4 = (char *)arr;//强制类型转换为char*,输出是应强制转化回来
	int *t[4];
	t[0]=(int *)&arr[5];//三维数组中的第六个二维数组的第一个地址
	t[1]=(int *)&arr[1][1][0];//三维数组中的第二个二维数组的第二个一维数组的第一个数的地址
	t[2]=t[0];//和t[0]一样
//	t[3]=(int *)&t[0];//有问题
    int**p;//修改一下,也可以输出时强制类型转换:printf("%d\n",**(int**)t[3]);
    p=t[3];
//或者
    t[3]=(int*)&(*t[0]);

​

        可以看出long *pr3=arr和t[3]有问题,并且long *pr3=arr没有强制类型转化,编译器可能会警告.

        然后进行解答

​
// &arr[0][0][0]//A
// &arr[0][0][1]//A+1*4
// &arr[0][1][0]//A+3*4
// &arr[1][0][0]//A+15*4
// &arr[1][0][1]//A+16*4
// &arr[0][1][1]//A+4*4
// arr//A
// **arr+1//A+1*4
// *(*arr+1)//A+3*4
// **(arr+1)//A+15*4
// *(*arr+1)+1//A+4*4
// *(*(arr+1))+1//A+16*4
// &arr[1][0][0]+1//A+16*4
// &arr[0][2]+1//A+9*4
// &arr[1]+1//A+(15+15)*4=A+30*4
// &arr+1//A+150*4
// pr1+2//A+2*4
// pr2+2//A+2*4
// pr3+2//A+2*8
// pr4+2//A+2*1

​
// t//B,A
// t+1//A+72
// t[0]+1//A+4
// &t[0]+1//A+72
// *(&t[0]+1)+1//A+72+4
// t[1]//A+72
// (int(*)[4])t[1]//A+72
// ((int(*)[4])t[1])+1//A+72+16
// *((int(*)[3][4])t[2]+1)+1//A+64
// t[3]//B
// t[3]-1//B-4

四.查看二进制

                输出⼀个 int 类型变量的⼆进制表⽰(要求:不进制转换)

#include<stdio.h>
void Binary(int num)//或者直接用循环/2的方法
{
    int i;
    for(i=31;i>=0;i--)//int类型下一共32位
    {
        int tnum=num;
        tnum=num&(1<<i);
        printf("%d",tnum>>i);
        if(i%8==0){
            printf(" ");
        }
    }
    printf("\n");
}

int main(){
    int num=0;
    scanf("%d",&num);
    Binary(num);

    return 0;
}

五.宏函数

        下⾯代码希望通过宏函数交换两个 int 类型的变量,请结合你对宏函数的理解,修改下⾯的代码,并谈 谈下⾯的代码的缺陷。

#define swap(x,y) int tmp=x;x=y;y=tmp;

        首先,要知道宏是在预处理阶段的整体替换,不是函数,其次swap(x,y)习惯上会加“;”,而实际上,这个宏定义不需要‘;’,否则会出错,可以用do{}while(0)来避免。

#define swap(x,y) do{int temp=x;x=y;y=temp;}while(0)

        注意,尽量避免在宏定义中++或--;

六.结构体内存对⻬(浅谈)

        请谈谈你对 C 语⾔中, 结构体 中成员在内存中的存储⽅式的理解。

        我们首先来看一个例子:

#include<stdio.h>
typedef struct {
    int a;
    char b;
    double c;
    char d[6];
}A;
typedef struct{
    char b;
    double c;
    int a;
    char d[6];
}B;

int main(){
    A A1;
    B B1;
    printf("A=%d\nB=%d\n",sizeof(A1),sizeof(B1));
//A=24,B=32

    return 0;
}

        可以看出,A和B的数据类型是一样的,但占用的内存却不一样,但有一点是一样的,那就是结果都是8的倍数。

        这就是内存对齐的结果,那么具体什么是内存对齐?为什么要内存对齐?

 1.什么是内存对齐:

        为了方便计算和说明,我们假设两个结构体都是从内存0开始的(实际并不可能),

        对于结构体A:a占0~3的地址,b占4的地址,c是double类型,而4不是8的倍数,所以空下来4~7,从8开始,占据了8~15的地址,d是char类型长度为6的字符串,占据了16~21的地址,共24个地址。

        对于结构体B:b占据了0~1的地址,c占据8~15的地址,a占据16~19的地址,d占据24~30的地址,然后发现最大数据类型为double,大小为8,所以补充(其实没有任何东西)至31,共32个地址。  

        这就是内存对齐,对齐规则是按照成员的声明顺序,依次分派内存,其偏移量为成员大小的整数倍,0看做任何成员的整数倍,最后结构体的大小为最大成员的整数倍。 

        可以注意到的一点就是我A中的d是从16开始的,可是16不是6的倍数啊,但16是8的倍数,是一个从寻址的长度(64位操作系统下),而内存对齐就是为了寻址方便,这就涉及到内存对齐的原理了。

2.内存对齐的原理(其实只是半个原理,更深的我也没完全搞懂):

        计算机是以字节(Byte)为单位划分的,用cpu来访问某一字节的数据,但是cpu的寻址是通过地址总线来访问内存的,cpu又有32位和64位的区别,32位4字节,64位8字节,那么CPU实际寻址的长度就是4个字节,也就是只对地址是4的倍数的内存地址进行寻址,64位同理。

        一个变量的数据存储范围是在一个寻址长度范围内的话,一次寻址就可以读取到变量的值,如果是超出了步长范围内的数据存储,就需要读取两次寻址再进行数据的拼接,效率明显降低了。

        为了加快访问效率,减少不必要的消耗,就有了内存对齐。

       参考: 浅谈CPU内存访问要求对齐的原因 – 仰望苍天思寰宇

七.编译

        请谈谈你对 C 语⾔使⽤中,「从源码到可执⾏⽂件」的过程的理解。(不是我说,这东西贼多,这里我就大概说说)

        「从源码到可执⾏⽂件」的过程:源程序->编译预处理->编译->汇编程序->链接程序->可执行文件

 1.预处理:处理以#开头的指令,比如头文件,宏定义;还有除去注释;条件预编译指令。

C语言的宏替换和文件包含的工作,不归入编译器的范围,而是交给独立的预处理器。

  2.编译阶段 : 需要进行三个步骤:词法分析、语法分析和语义分析。

        编译的过程实质上是把高级语言翻译成汇编语言的过程。

        具体内容太多,就不过多解释了,这里放一个链接,有兴趣的可以去看:编译原理之词法分析、语法分析、语义分析_nic_r的专栏-CSDN博客_词法分析和语法分析的区别

3.汇编:将汇编代码转变成机器可以执行的指令(机器码文件)。

  c语言编译,汇编会产生xxx.o的文件(unix和类unix),windows则是.obj.

4.链接:将翻译成的二进制与需要用到库绑定在一块。

         链接可以执行与编译时(源代码被翻译成机器代码时),也可以执行与加载时(在程序被 加载器加载到存储器并执行时),甚至执行与运行时,由应用程序来执行.

        链接由链接来完成,链接器还有静态链接器和动态链接器。

        更深入的可以看看这个(偷个懒):c语言--从.c文件到可执行文件,其间经历了几步?O1,O2,O3是什么?_命z的博客-CSDN博客_.c文件到可执行文件经历哪些步骤

八. 括号匹配问题       

                编写⼀个函数,判断⼀个字符串中的括号是否匹配。

        字符串中只有 ( 、 ) 、 [ 、 ] 、 { 、 } 这 6 字符, ( 必须与 ) 匹配, [ 必须与 ] 匹配, { 必须与 } 匹配。

例如:

{[()]()} 中的括号就是匹配的

([]{)}} 中的括号是不匹配的

请完善下⾯的函数:

// 函数接⼝定义

// 若匹配,返回1;若不匹配,返回0

int match(char *str);

        先判断是否是偶数,不是偶数肯定不配对,然后我的思路是将左右括号分开。

#include<stdio.h>
#include<string.h>
char left(char s){
    if(s==')'){
        return '(';
    }
    if(s==']'){
        return '[';
    }
    if(s=='}'){
        return '{';
    }
    return 0;
}
int match(char*str){
    int l=strlen(str);
    if(l%2!=0){
        return 0;
    }
    char arr[l+1];
    memset(arr,0,l+1);
    int k=0;
    char ch;
    for (int  i = 0; i < l; i++)
    {   
        ch=left(str[i]);
        if(ch){
            if(k==0||ch!=arr[k-1]){
               return 0;
            }
           k--;
        }else{
           arr[k++]=str[i];
        }
    }
    return k==0;
}

int main(){
    char*str="{[()]}";
    int bool=match(str);
    printf("%d\n",bool);

    return 0;
}

九. 判断链表是否有环

        请以你能想到的最优的办法判断链表中是否有环。

// 链表结点结构体的定义
struct Node
{
    int val;
    struct Node *next;
};
#include<stdio.h>
typedef struct node
{
    int val;
    struct node *next;
}Node;
//使用双指针
//一个快,一次移动两个节点,一个慢一次移动一个节点

void isloop(Node*head){
    Node *i,*j;
    i=head;//慢
    j=head;//快
    
    while(j!=NULL&&j->next!=NULL){//注意:这里用快的判断
        i=i->next;
        j=j->next->next;
        if(i==j){
            printf("it is loop.");
        }
    }
    printf("it not is loop.");
}

十. 递归反转链表

        请使⽤ 递归 实现对链表的反转。        

typedef struct node
{
    int val;
    struct node *next;
}Node;

Node* reverse(Node*head){
    if( head==NULL || head->next==NULL ){
        return head;
    }
    Node* NewHead=reverse(head->next);

    head->next->next=head;
    head->next=NULL;//防止成环

    return NewHead;
}

//以下为测试用
void print(Node*head){
    Node*d=head;
    while(d!=0){
        printf("%d ",d->val);
        d=d->next;
    }
    printf("\n");
}

int main(){
    Node*head=NULL;
    Node*p;
    Node*last=NULL;
    int num=0;

    do{
        scanf("%d",&num);
        if(num!=-1)
        {
            p=(Node*)malloc(sizeof(Node*));
            p->val=num;
            p->next=NULL;
            if(head){
                last->next=p;
            }
            else{
                head=p;
            }
            last=p;
        }
    }while(num!=-1);

    print(head);

    head=reverse(head);
    print(head);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值