一、单选题共14题,合计70分

1(5分)C语言的标识符只能由字母、数字和下划线三种字符组成,且首字符( A ) [单选题] *

A. 必须为字母或下划线

B. 必须为下划线

C. 必须为字母

D. 可以是字母、数字和下划线中的任一种字符。
2(5分)他人修改源代码后必须开源的许可证有 ( A ) [单选题] *

A. LGPL

B. BSD

C. Apache

D. MIT

开源许可

3(5分)列出当前目录下所有.h的文件,不含子目录 [单选题] * A

A. ls *.h

B. find . -name *.h

C. find . -name *.h$

D. find . |grep h
4(5分)一个类的友元函数可以访问类的(  D  )成员。

A. 私有成员

B. 保护

C. 公有

D. 以上都正确
5(5分)统计当前目录中头文件总数[单选题] * B

A. find . |grep h

B. find . -name *.h | wc -l

C. find . |grep h |wc -l

D. find . -name *.h
6(5分)下面字面值数据类型说法错误的是?( C )

A. L’a’表示为宽字符型字面值a且类型是wchar_t 
#  'a'表示字符a, L'a'表示宽字符型字面值a且类型是wchar_t, "a"表示字符串a, L"a"表示宽字符型字符串a。

B. 10L表示一个长整形数
#    10是一个普通的整数类型字面值, 10u表示一个无符号数, 10L表示一个长整型数, 10uL表示一个无符号长整型数, 012是一个八进制数(对应的十进制数是10), 0xC是一个十六进制数(对应的十进制数是12)。

C. 3.14L表示一个long float类型的扩展精度浮点数
#    3.14是一个普通浮点类型字面值, 3.14f表示一个float类型的单精度浮点数, 3.14L表示一个long double类型的扩展精度浮点数。

D. 10. 是一个浮点数
#    10是一个整数, 10u是一个无符号整数, 10.是一个浮点数, 10e-2是一个科学计数法表示的浮点数,大小为10*10-2 = 0.1.
7(5分)以下不是C语言提供的合法关键字为( B ) [单选题] *

A. switch

B. printf
#    printf 是函数不是关键字

C. case

D. default
8(5分)以下程序运行时,若从键盘输入5,则输出结果是( C ) [单选题] *
main(){
    int a=5;
    if(a++ > 5) // a++ 是先用后加,现在a目前还是5,表达式为假,会执行else。然后a+1 也就是6。
        printf("%d\n", a);
    else
        printf("%d\n", --a); // --a是先减后用,目前a是6, 减一后为5,输出
    return 0;
}

A. 7

B. 4

C. 5

D. 6
9(5分)龙芯CPU的架构是( B ) [单选题] *

A. x86

B. mips

C. ARM

D. Alpha
10(5分)以下不是开源许可证的有( C ) [单选题] *

A. MIT

B. GPL

C. CC

D. BSD

开源许可

11(5分)假设txt_size 是一个无参数函数,它的返回值是int;Unsigned buf_size = 1024;下面哪个定义是合法的?( B )

A. int ia[buf_size];
#    非法,中括号内必须是常量表达式

B. int ia[4*7 -14];

C. int ia[txt_size()];
#    txt_size()只有在编译的时候才能确定其值

D. char st[11] = “fundamental”;
#    没有空间存放空字符,结尾需要存一个'\0'。 应定义为 char st[12] = “fundamental” .
12(5分)使用地址作为实参传给形参,下列说法正确的是( D )。

A. 实参是形参的备份

B. 实参与形参无联系

C. 形参是实参的备份

D. 实参与形参是同一对象
#    地址作为实参,表示实参与形参代表同一个对象。如果实参是数值,形参也是普通变量,此时形参是实参的备份。
#    传值和传址的区别
13(5分)对数组名作函数的参数,下面描述正确的是( B )。

A. 数组名作函数的参数,调用时将实参数组复制给形参数组
#    传递的是指向数组首地址的指针变量

B. 数组名作函数的参数,主调函数和被调函数共用一段存储单元

C. 数组名作参数时,形参定义的数组长度不能省略
#    数组长度不起作用。数组传递的是指向数组首地址的指针变量

D. 数组名作参数,不能改变主调函数中的数据
#    传递的是指针,可以改变主调函数的数据
#    传值和传址的区别
14(5分)下面哪一个不是一种临界区保护机制:C
#    临界区的概念是因为并发编程(multiprogram)的出现导致的,当出现多个task、多个cpu、甚至网络中多个服务器对同一个逻辑对象操作时,就会有条件竞争出现,如果设对该逻辑对象的操作为A,此时必须对A做特殊保护,约定对这A的这种特殊保护统称为临界区保护(mutex exclue),称A操作的范围为临界区。

A. 互斥锁

B. 信号量

C. 条件变量

D. 读写锁

考试环境与要求:

云主机

  • system: deepin-20
  • 用户名:desk 密码:123

考试项目下载地址:

考试要求:

  • 下载考试项目压缩文件到虚拟机/home/desk/Desktop桌面
  • 在桌面创建目录devexam, 并解压下载的tgz文件里的考题(如:exam1,exam11等)存放到devexam里(如:/home/desk/Desktop/devexam/{exam1,exam11等})
  • 共三道考题
  • 题目和考试要求详见各个题目的main.cpp文件的注释说明
  • 自行安装考试所需要的程序开发环境和编译依赖
  • 使用C,C++语言完成所有考题

A卷题:

 第1题:
 * -------------------------------------------------------------------------------------------------------------------
 * 题目
 * -------------------------------------------------------------------------------------------------------------------
 * 无重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合,字符串每个字符均不相同。并将结果按字符串从小到大排序
 * 
 * 示例1:
 * - 输入:S = "qwe"
 * - 输出:["eqw", "ewq", "qew", "qwe", "weq", "wqe"]
 * 
 * 示例2:
 * - 输入:S = "ab"
 * - 输出:["ab", "ba"]
 * 
 * 要求:
 * 1. 使用纯C语言修改exam.h文件,通过所有单元测试。
 * 2. 代码逻辑符合题目要求,判卷评分有更多单元测试项。
 * 3. 输出结果按照字典顺序排序。
 * 4. exam.h以外的文件不得修改,修改会被覆盖。
#ifndef EXAM_H
#define EXAM_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char** permute_string(const char* input);
void sort(char* S,int len);
void DFS(char *S, int *visited,  char *buf, char **res, int *index, int idx);

// main用于自己测试用,考试请勿写入。
int main()
{
    const char* input = "ab";
    char **s = permute_string(input);
    int n = 1;
    /* 计算输出长度 */
    for (int i = 1; i <= strlen(input); i++) {
        n *= i;
    }
    for(int i = 0; i < n; i++)
    {
        puts(s[i]);
    }
    return 0;
}

/* 返回一个以空字符串结尾的char*数组 */
char** permute_string(const char* input)
{

    // 获取字符串长度。
    int len = strlen(input);
    int n = 1;
    int i;
    /* 计算输出长度 */
    for (i = 1; i <= len; i++) {
        n *= i;
    }
    
    /* 因为题目中给的是const char,这里将const char* 改为char*,方便排序 */
    char *S = (char*)malloc(sizeof(char) * (len + 1) );
    /* 复制字符串并且排序  */
    strcpy(S, input);
    // 冒泡排序
    sort(S, len);
    
    int N = 7; // 应题目要求
    // 返回数组
    char **res   = (char**)malloc(sizeof(char*) * N);
    res[N-1] = 0; // 应题目要求。
     // 定义一个数组,用来来标识S[i]是否已经选取
    int *visited = (int*)malloc(sizeof(int) * len);
    memset(visited, 0, sizeof(int) * len);
    // 用于临时存储结果
    char *buf    = (char*)malloc(sizeof(char) * (len + 1));
    buf[len] = '\0';
    // res的下角标
    int *index = (int*)malloc(sizeof(int));
    *index = 0;
    
    DFS(S, visited, buf, res, index, 0);
    return res;
}

/* 冒泡排序  */
void sort(char* S,int len){
    int i =0, j = 0;
    char temp = '\0';
    for(i = 0 ; i < len-1; i++)
    {
        for(j = i+1 ; j < len; j++)
        {
            if(S[i] > S[j])
            {
                temp = S[i];
                S[i] = S[j];
                S[j] = temp;
            }
        }
    }
}

/* DFS  */
void DFS(char *S, int *visited,  char *buf, char **res, int *index, int idx){
    // 1. 截止条件
    if(strlen(S) == idx){
           res[*index] = (char*)malloc(sizeof(char) * (strlen(S) + 1));
        strcpy(res[*index], buf);
        (*index)++;
        return;
    }
    // 2. 遍历候选节点
    for(int i = 0; i < strlen(S); i++){
        // 2.1 筛选
        if(!visited[i]){
            buf[idx] = S[i];
            visited[i] = 1;
            DFS(S, visited, buf, res, index, idx+1);
            visited[i] = 0;
        }
    }
}
#endif // EXAM_H
第2题:
 * -------------------------------------------------------------------------------------------------------------------
 * 题目
 * -------------------------------------------------------------------------------------------------------------------
 * 
 * 遵循 https://specifications.freedesktop.org/desktop-entry-spec/latest/ 标准实现一个desktop文件解析工具
 * 
 * 功能要求
 * - desktop文件格式无错误时进程退出码为0
 * - 实现对desktop文件Name(程序名称)、Exec(可执行文件)、Icon(图标文件)三个字段的解析
 * - 需要检测文件的格式,遇到错误时以退出码 1 退出进程
 * - 输入接收一个desktop文件的绝对路径,能够输出程序名称、图标文件(图标文件不考虑从系统图标主题中查找)
 * - 能够在指定一个desktop文件后启动进程,且允许为其指定参数
 * 
 * 其它要求
 * - 使用cmake构建工程
 * - 可执行文件位置和名称为:./src/freedesktop
 * - testHelper.sh有很少的测试用例,可以帮助做初步验证
 * 
 * 示例
 * - a.desktop文件内容为:
 *     [Desktop Entry]
 *     Name=A;
 *     Name[zh_CN]="我是A";
 *     Exec=cat %f
 *     Icon=/tmp/a.png
 * 
 * - 输入参数(-d指定desktop文件的绝对路径,-n表示要获取应用程序名称):
 *     freedesktop -d /home/a/a.desktop -n
 * - 输出结果:
 *     A或我是A(中文环境下,注意应该去掉引号)
 * 
 * - 输入参数(-i表示要获取应用程序的图标)
 *     freedesktop -d /home/a/a.desktop -i
 * - 输出结果(如图标数据不是一个文件路径,则先将图标文件保存为文件后再返回此文件的绝对路径):
 *     /tmp/a.png
 * 
 * - 输入参数(-e表示要启动此应用程序,其后可根一个或多个传入参数)
 *     freedesktop -d /home/a/a.desktop -e /home/a/test.txt
 * - 输出结果:
 *     输出结果为 /home/a/test.txt 文件的内容,也就是Exec命令执行时对标准输出通道所写入的全部数据
#include<iostream>
#include<unistd.h>
#include<cstring>
#include<string>

/* 定义了一个字符串拷贝函数。将str2从beg开始到end赋值给str1; beg和end为下角标 */
void strcopy(char* str1, char*str2, int beg, int end);

int main(int argc, char** argv)
{

    char tmp[1024];
    int flag[5] = {0};
    char *name, *exec, *icon;
    int i;
    for(i = 1;i < argc; i++)
    {
        /* -d 文件绝对路径带解析功能 */
        if( strcmp( argv[i], "-d" ) == 0 ) 
        {
            FILE *p_file = fopen(argv[i+1], "r");
            while(fgets(tmp, sizeof(tmp), p_file) != NULL )
            {
                tmp[strlen(tmp)-1] = '\0';
                if( strncmp( tmp, "[Desktop Entry]", 15) == 0 ) flag[0] = 1;
                if( strncmp( tmp, "Name=", 5) == 0)
                {
                    char *Name = (char*)malloc(sizeof(char) * ( strlen(tmp) - 5 ) );
                    strcopy(Name, tmp, 5, strlen(tmp));
                    if( Name[strlen(Name) - 1 ] == ';' || Name[strlen(Name) - 1 ] == '\n' ) Name[strlen(Name) -1] = '\0';
                    name = Name;
                    flag[1] = 1;
                }
                if( strncmp( tmp, "Exec=", 5) == 0)
                {
                    char *Exec = (char*)malloc(sizeof(char) * ( strlen(tmp) - 5 ) );
                    strcopy(Exec, tmp, 5, strlen(tmp));
                    if(Exec[strlen(Exec) - 1 ] == ';' || Exec[strlen(Exec) - 1 ] == '\n') Exec[strlen(Exec) -1] = '\0';
                    exec = Exec;
                    flag[2] = 1;
                }
                if( strncmp( tmp, "Icon=", 5) == 0)
                {
                    char *Icon = (char*)malloc(sizeof(char) * ( strlen(tmp) - 5 ) );
                    strcopy(Icon, tmp, 5, strlen(tmp));
                    if(Icon[strlen(Icon) - 1 ] == ';' || Icon[strlen(Icon) - 1 ] == '\n') Icon[strlen(Icon) - 1] = '\0';
                    flag[3] = 1;
                    icon = Icon;
                }
                if( strncmp( tmp, "Name[zh_CN]=", 5) == 0)
                {
                    flag[4] = 1;
                }
            }

            fclose(p_file);
            // 遇到错误时以退出码 1 
            if( ! ( flag[0] == 1 && flag[2] == 1 && flag[3] == 1 && (flag[4] == 1 || flag[1] == 1) ) )
                exit 1;        
        }
    }

    for(i = 1 ; i < argc; i++)
    {
        
        /* -n 获取名称 */
        if( strcmp( argv[i], "-n" ) == 0 )
        {
            std::cout << name << std::endl;
        }
        /* -i Icon 路径获取  */
        if( strcmp( argv[i], "-i" ) == 0 )
        {
             std::cout << icon << std::endl;
        }   
        /* -e 运行  */
        if( strcmp( argv[i], "-e" ) == 0 )
        {
            std::string s = "cat ";
            std::string temp;
            int j = i + 1;
            while( argv[j] != NULL )
            { 
                temp = argv[j];
                s += temp;
                s += " "; 
                j++;
            }
            char cmdTemp[1024];
            strcpy(cmdTemp,s.c_str());
            FILE *pp = popen(cmdTemp, "r");
            char tmp[1024];
            while(fgets(tmp, sizeof(tmp), pp) != NULL)
            {
                std::cout << tmp ;
            }
            pclose(pp);
        }
    }    
    return 0;
}

void strcopy(char* str1, char*str2, int beg, int end)
{
    int i;
    for( i = 0; i < end - beg + 1 ; i++ )
    {
        str1[i] = str2[ beg + i]; 
    }
}
第3题:(30分)
 -------------------------------------------------------------------------------------------------------------------
 题目
 -------------------------------------------------------------------------------------------------------------------

 编写一个程序,从命令行读取一个软件包的名称,调用“dpkg -L”命令获得该软件包的文件列表,并检查所有文件。
 要求:

  1. 使用cmake和c/c++开发
  2. 可执行文件名称和位置是./src/pkgverify
  3. 检查该软件包所有文件,确认:

   - 路径存在
   - 文件存在
   - 如果是符号链接,链接的文件也存在
   - /usr路径下的文件,确认文件属主都是root

  4. 输出所有有问题的文件或路径名
  5. 由于qDebug/qInfo等输出方式会添加多余字符,且不在标准输出上,与考试要求不符,请谨慎使用
  6. testHelper.sh有很少的测试用例,可以帮助做初步验证

 示例:
 如果/etc/cups路径被删除了,则运行结果如下:
 pkgverify cups
 /etc/cups
 /etc/cups/snmp.conf
#include <iostream>
#include <unistd.h>
#include <sys/stat.h>
#include <cstring>

int main(int argc, char** argv)
{
    // 调用系统命令,最小化数组,节省空间
    int len = strlen(argv[1]) + 9;
    char str1[len] = "dpkg -L ";
    strcat(str1, argv[1]);
    // 调用系统命令,并取得返回值    
    FILE *pp = popen(str1, "r");
    if (!pp)
        return 1;
    // 返回值数组
    char tmp[1024];
    // 初始化stat结构体buf、返回值(retRes用于判断文件是否存在);
    // buf2 用于链接文件的返回存储。事实上这里仅仅为了调用,并不进行实际上的作用;
    int retRes = 0;
    struct stat buf;
    char buf2[1024];
    
    // 逐行读取所有的dpkg -L返回值
    while(fgets(tmp, sizeof(tmp), pp) != NULL)
    {
        // 末尾包含 "\n",更改为"\0"结束,后面可能会有问题。
        tmp[strlen(tmp)-1] = '\0';
        retRes = lstat(tmp, &buf);
        
        // 判断 文件和文件路径 是否存在;
        if(retRes == 0)  // 存在
        {
            // - 如果是符号链接
            if(  S_ISLNK(buf.st_mode)   )
            {
                // 检测链接文件是否存在,不存在输出
                int retResult = readlink(tmp,buf2,sizeof(buf2));
                if(retResult == -1)
                {
                    std::cout << tmp << std::endl;
                }
            }

            // - /usr路径下的文件,确认文件属主都是root
            if( strncmp("/usr", tmp, 4) == 0 )
            {
                if( buf.st_uid !=0 && buf.st_gid != 0)
                {
                    std::cout << tmp << std::endl;
                } 
            }
 
        }
        else // 文件不存在
        {
            std::cout << tmp << std::endl; // can join each line as string
        }
        
    }    
    pclose(pp);
    return 0;
}

相关知识:

struct stat {
  dev_t     st_dev;    //文件的设备编号
  ino_t     st_ino;    //节点
  mode_t    st_mode;   //文件的类型和存取的权限
  nlink_t    st_nlink;   //连到该文件的硬连接数目,刚建立的文件值为1
  uid_t     st_uid;    //用户ID
  gid_t     st_gid;    //组ID
  dev_t     st_rdev;   //(设备类型)若此文件为设备文件,则为其设备编号
  off_t     st_size;   //文件字节数(文件大小)
  unsigned long st_blksize;  //块大小(文件系统的I/O 缓冲区大小)
  unsigned long st_blocks;  //块数
  time_t    st_atime;   //最后一次访问时间
  time_t    st_mtime;   //最后一次修改时间
  time_t    st_ctime;   //最后一次改变时间(指属性)
};


// 先前所描述的st_mode 则定义了下列数种情况:

    S_IFMT   0170000    文件类型的位遮罩
    S_IFSOCK 0140000    scoket
    S_IFLNK 0120000     符号连接
    S_IFREG 0100000     一般文件
    S_IFBLK 0060000     区块装置
    S_IFDIR 0040000     目录
    S_IFCHR 0020000     字符装置
    S_IFIFO 0010000     先进先出
    S_ISUID 04000     文件的(set user-id on execution)位
    S_ISGID 02000     文件的(set group-id on execution)位
    S_ISVTX 01000     文件的sticky位
    S_IRUSR(S_IREAD) 00400     文件所有者具可读取权限
    S_IWUSR(S_IWRITE)00200     文件所有者具可写入权限
    S_IXUSR(S_IEXEC) 00100     文件所有者具可执行权限
    S_IRGRP 00040             用户组具可读取权限
    S_IWGRP 00020             用户组具可写入权限
    S_IXGRP 00010             用户组具可执行权限
    S_IROTH 00004             其他用户具可读取权限
    S_IWOTH 00002             其他用户具可写入权限
    S_IXOTH 00001             其他用户具可执行权限

    上述的文件类型在POSIX中定义了检查这些类型的宏定义:

    S_ISLNK (st_mode)    判断是否为符号连接
    S_ISREG (st_mode)    是否为一般文件
    S_ISDIR (st_mode)    是否为目录
    S_ISCHR (st_mode)    是否为字符装置文件
    S_ISBLK (s3e)        是否为先进先出
    S_ISSOCK (st_mode)   是否为socket

int main() {
  struct stat buf;
  stat("/etc/hosts", &buf);
}

标签: none