• C语言 面试


    P1(多选)
    有如下定义 int a; int *b; 则下列哪些语句是正确的:

    A: b=&a;
    B: b=*a;
    C: b=(int*)a;
    D: *b=a;


    思路如下:b是一个int类型指针,a是int变量,二者的关系很清晰,选AD。


    P2(单选)
    有如下定义
    char* const s1 = "string";
    char const *s2 = "string"; // 这句也等于 const char* s2 = "string";
    则下列哪些语句是正确的:

    A: s1 = "w";
    B: *s1 = 'w';
    C: s2 = "w";
    D: *s2 = 'w';


    思路如下:s1是常量指针,指向了一个常量字符串。本身的指向不可改,而指向的那段内容因为也是常量,也不可改。AB操作都是有问题的。
    s2是指向了一个常量字符串的可变指针,对其指向做改变是可以的,想提领其内容并修改是错误的。选C。


    P3(多选)
    有如下定义
    char* s1 = "string";
    char s2[] = "string";
    则下列哪些语句是正确的:

    A: *s1 = 'w';
    B: s1 = "w";
    C: *s2 = 'w';
    D: s2 = "w";


    思路如下:s1是指向一个常量字符串的char型指针,本身的指向是可以改的;
    s2是一段长度固定的缓冲区,我们可以对其提领改变它的内容,不能改变其指向,因为s2这个名字和这段缓冲区是一体的,如要改变缓冲区,需要string.h系列api。
    选BC。


    P4(单选)
    有如下定义
    #define SQUARE(x) x*x
    SQUARE(SQUARE(1+1))*2的值是:

    A:6
    B:10
    C:18
    D:32


    思路如下:做这道题有很大迷惑性,我也是看了运行结果后才明白,解题的时候思维定式的加了括号,实际完全不考虑括号就可以写成:
    1+1*1+1*1+1*1+1*2 = 6,选A。


    P5(单选)
    一个“指向整形数组的指针”的定义为:

    A: int (*ptr)[]
    B: int *ptr[]
    C: int *(ptr[])
    D: int ptr[]


    实在无力,这道题我觉得B和C都对,大家帮帮忙吧


    P6(单选)
    b=2;c=1 下列哪些语句使a的值为1:

    A: a=b++;
    B: a=++b;
    C: a=++(c+1);
    D: a=(b+1)++;
    E: 以上都不是


    思路如下:各项累加结果都超过1了,没有正确的,所以选E。


    P7(单选)
    有如下定义char tt[256];那么sizeof(tt)的值是:

    A: 1
    B: 256
    C: 4
    D: 0


    思路如下:由于变量是char型数组,自然字节数就是256*sizeof(char),结果是256,选B。


    P8(单选)
    请写出以下程序的输出结果
    main()
    {
    char text[] = "abcdefghijklmno";
    int i = 0;
    text[12] = '';// m被清除
    while( text[++i] != '' )// 呈现隔一出一的趋势,1,3,5...
    {
    printf("%c", text[i++] );
    }
    }

    A:"acegik"
    B:"bdfhjln"
    C:"bdfhjl"
    D:"acegiko"


    思路:通过加的注释看到规律,当进行到mn的时候,虽然m置零了,但是不妨碍访问它的后续元素,选B。


    P9和P10基于下列程序:
    //这是一个用冒泡法排序的程序,请在空格处填入适当代码
    #include<stdio.h>
    #include<string.h>


    void sort( char s[], int nNum )
    {
    int i,j;
    char temp;
    for( i = 0; i < nNum; i++ )
    {
    for( j = i+1; j < nNum; j++ )
    {
    if( s[i] < s[j] )
    {
    temp = s[i];
    P9;
    P10;
    }
    }
    }
    }
    void main()
    {
    char str[]="olwf";
    sort(str, 4);
    printf("%s",str);
    }

    P9:(单选)
    A: s[i] = s[j];
    B: s[j] = temp;
    C: s[j] = s[i];
    D: temp = s[j];
    P10:(单选)
    A: s[i] = s[j];
    B: s[j] = temp;
    C: s[j] = s[i];
    D: temp = s[j];


    思路:这个冒泡算法还是很清晰的,选A和B。




    //以下问题P11-P20将在内存管理,堆栈管理,森林以及二叉树的C语言实现进行调试
    #include<stdio.h>
    #include<string.h>
    //以下是对于常数、类型和函数指针的定义
    #define MEMORY_SIZE102400
    typedef void*  PTR;
    typedef unsigned charBYTE;
    typedef PTR (*NodeFunc)(void);
    typedef void (*DoFunc)(int n,PTR p);
    //以下是对内存管理的类型定义和处理函数定义
    typedef struct Memory*PCMemory;
    typedef struct Memory
    {
    BYTE amem[MEMORY_SIZE];
    int  nMemPoint;
    }CMemory;
    void MInit(PCMemory pmem); // 内存初始化
    PTR MAlloc(PCMemory pmem, int nSize); // 内存分配函数
    // 以下是对堆栈管理的类型定义和处理函数定义
    typedef struct Stack*PCStack;
    typedef struct Stack
    {
    PTR pContent;
    PCStack pNext;
    }CStack;
    PCStack SNew(); //堆栈初始化
    PCStack SPush(PCStack pstk, PTR p);//堆栈进站
    PCStack SPop(PCStack pstk,PTR* pp); //堆栈出栈
    //以下是对用二叉树表示的森林处理的类型定义和处理函数定义
    typedef struct Tree*PCTree;
    typedef struct Tree
    {
    PTR pContent;
    PCTree pSon;
    PCTree pBrother;
    }CTree;
    PCTree TNew();//初始化一个森林
    // 向根节点或者子树节点增加儿子或兄弟
    void TAddSon(PCTree ptree,NodeFunc f);
    void TAddBrother(PCTree ptree,NodeFunc f);
    // 使用递归方法和堆栈方法对数进行遍历
    void TBrowse(PCTree ptree, DoFunc f, int n);
    void TBrowseStack(PCTree ptree, DoFunc f, int n);
    // 以下是内存实体的全局变量定义
    static CMemory mem;
    PCMemory pmem=&mem;
    // 以下是内存管理的处理函数的实现
    void MInit(PCMemory pmem)
    {
    pmem->nMemPoint = 0;
    }// MInit
    PTR MAlloc(PCMemory pmem,int nSize)
    {
    int nCurMemPoint;
    nCurMemPoint=pmem->nCurMemPoint;
    pmem->nCurMemPoint += nSize;
    return P11;
    }// MAlloc
    // 以下是堆栈处理的处理函数的实现
    PCStack SPush(PCStack pstk,PTR p)
    {
    PCStack pstkNew;
    pstkNew=MAlloc(pmem,sizeof(CStack));
    pstkNew->pContent=p;
    pstkNew->pNext=pstk;
    return P13;
    }// SPush
    /PCStack Spop(PCStack ptsk, PTR* pp)
    {
    if( ptsk != NULL )// P14
    {
    *pp = ptsk->pContent;
    ptsk = ptsk->pNext;
    return ptsk;
    }
    return NULL;
    }// Spop
    PCStack SNew() 
    { return NULL; }
    // 以下是用二叉树表示森林的处理函数的实现
    PCTree TNew()
    {
    PCTree pRoot;
    pRoot = MAlloc(pmem,sizeof(CTree));
    pRoot->pBrother=NULL;
    pRoot->pSon=NULL;
    pRoor->pContent="ROOT";
    return pRoot;
    }// TNew // P15
    void TAddSon(PCTree ptree, NodeFunc f)
    {
    PTR p=f();
    PCTree ptreeNew;
    if( p==NULL )
    TAddBrother(ptree,f);
    else
    {
    ptreeNew=MAlloc(pmem,sizeof(CTree));
    ptreeNew->pBrother=NULL;
    ptreeNew->pSon=NULL;
    ptreeNew->pContent=p;
    ptreeNew->pSon= ptreeNew;
    TAddSon(ptreeNew,f);
    P16-1; // P16
    }
    }// TAddSon
    void TAddBrother(PCTree ptree,NodeFunc f)
    {
    PTR p = f();
    PCTree ptreeNew;
    if( p==NULL )
    return;
    else
    {
    ptreeNew=MAlloc(pmem,sizeof(CTree));
    ptreeNew->pBrother=NULL;
    ptreeNew->pSon=NULL;
    ptreeNew->pContent=p;
    ptreeNew->pBrother=ptreeNew;
    TAddSon(ptreeNew,f);
    P16-2 ; // P16
    }
    }// TAddBrother
    void TBrowse(PCTree ptree,DoFunc f,int n)
    {
    if( ptree != NULL )
    {
    P17; //P17
    TBrowse( ptree->pSon,f,n+1);
    TBrowse( ptree->pBrother,f,n);
    }
    }// TBrowse
    void TBrowseStack(PCTree ptree, DoFunc f, int n)
    {
    PCStack pstk=SNew();
    while(1)
    {
    P17;
    if(ptree->pSon==NULL)
    {
    P18; //P18
    {
    pstk=Spop(pstk,&ptree);
    n--;
    if(pstk==NULL)
    return;
    }
    ptree=ptree->pBrother;
    }
    else
    {
    pstk=SPush(pstk,ptree);
    n++;
    ptree=ptree->pSon;
    }
    }
    }// TBrowse
    //以下是测试用函数和数据
    static char* aaNode[22]=
    {
    "1","2","3",NULL,"4",NULL,"5",NULL,NULL,"6",NULL,"7","8",NULL,"9","10",NULL,"11",NULL,"12",NULL,NULL
    }; // P19
    static int nNode=0;
    PTR Node(){ return aaNode[nNode++]; }
    void Out(int n, PTR p)
    {
    int i;
    for(i=0;i<n;i++)
    printf("_");
    printf("%s ",p);
    }// Out


    void main()
    {
    PCTree ptree;
    MInit(pmem);
    ptree=TNew();
    TAddSon(ptree,Node);
    TBrowse(ptree->pSon,Out,0);
    TBrowseStack(ptree->pSon,Out,0); //P20
    }//main


    以下是正确的输出结果
    1
    _2
    __3
    __4
    __5
    _6
    _7
    __8
    __9
    ___10
    ___11
    ___12
    1
    _2
    __3
    __4
    __5
    _6
    _7
    __8
    __9
    ___10
    ___11
    ___12


    P11(单选)
    P11处应当写的语句是

    A:(void*)(&(pmem->amem[nCurMemPoint]))
    B:(void*)(pmem->amem[nCurMemPoint])
    C:(void*)(&(pmem->amem[pmem->nMemPoint]));
    D:(void*)(pmem->amem[pmem->nMemPoint])
    E:以上都不是


    思路如下:该函数主要功能是在缓冲区里划定一部分出来,然后游标记录缓冲区累加到已使用量,那么返回的可用内存部分起始地址就是上一次的游标所在的位置,选A。




    P12(多选)
    对于以上内存管理的类型定义和实现函数叙述正确的是:

    A:该类型定义的空间在程序的静态存储区,不能用于动态的内存分配,应该使用malloc等函数进行内存分配
    B:没有进行内存分配越界的检查
    C:不能用于对长整数和双精度整数进行内存分配,否则会导致内存错
    D:没有考虑计算机字长的影响,在Sun WorkStation等以字而不是字节为单位的机器上可能会导致内存错
    E:不能用于对用struct定义的复杂变量进行内存分配,否则会导致内存错


    思路如下:A,说法正确,这里的内存分配是表面的,实际是在一个静态大缓冲区里管理使用内存。
    B,说法正确,没有对nMemPoint做一个范围检查。
    CD两项,不太确定,大伙帮忙分析分析吧
    E,说法正确,struct结构体里如果有复杂的类型,那么实际的大小可能就超过了sizeof(struct)=4的大小,是存在出错隐患的。




    P13(单选)
    P13处应当写的语句是:

    A: pstk+1
    B: pstk->pNext
    C: pstk
    D: pstkNew
    E: 以上都不是


    思路如下:每调用push一次,就新增了一个PCStack元素,返回的就是最顶上的那一个,就是pstkNew,选D。




    P14(单选)
    P14处语句的作用是

    A:防止堆栈指针出现循环
    B:检查堆栈内容的合法性
    C:判断堆栈是否为空
    D:防止堆栈使用的内存溢出
    E:以上都不是


    思路:在操作目标指针之前,对其判空,如果无效指针,就会放弃操作,选C。




    P15(多选)
    根据这个创建函数可以判断,这个森林

    A:是一个按照内容排序的森林
    B:只能处理字符串
    C:有一个虚拟的根节点pRoot,真正的根节点是pRoot->pBrother
    D:有一个虚拟的根节点pRoot,真正的根节点是pRoot->pSon
    E:如果利用根节点的pBrother,可以处理多个森林的情况


    思路:从题目描述上看,用二叉树表示森林,就像把二叉树向左旋转45度;从main函数里看打印结果,操作数组,把1——12按顺序分配到各个节点,
    A正确;B错误;新建的这个Root节点,AddSon里开始从1号节点构造,在其后有 1——12号,1号节点是它们12个节点的最高层节点,真正根节点就是1号节点————Root的子节点,D正确;
    E正确,1号节点就是已经被利用的子节点了,那么也可以利用Root的Brother,再创建出一个森林来。


    P16(单选)
    P16-1和P16-2应当写的语句是

    A:[1]TAddBrother(ptree,f) [2]TAddBrother(ptree,f)
    B:[1]TAddBrother(ptree,f) [2]无
    C:[1]无 [2]TAddBrother(ptree,f)
    D:[1]TAddBrother(ptreeNew,f) [2]TAddBrother(ptreeNew,f)
    E:[1]TAddBrother(ptreeNew,f) [2] 无
    F:[1]无 [2]TAddBrother(ptreeNew,f)


    思路:这个递归现在还不是特别清晰,个人选的是E。




    P17(单选)
    P17处语句的功能利用函数指针是对树节点的内容进行处理,这条语句应当是

    A:f(n,ptree->pContent)
    B:f(n,ptree)
    C:f(n,&(ptree->pContent))
    D:f(n,&ptree)
    E:f()


    思路:这个地方是调用Out回调函数,打出元素值,第一个参数为n,第二项是一个指针类型,是pContent指向的内容,选A。




    P18 (单选)
    P18处语句的作用是

    A:while( ptree == NULL )
    B:if( ptree ==NULL )
    C:while( ptree->pSon == NULL )
    D:if( ptree->pSon == NULL )
    E:while( ptree->pBrother == NULL )
    F:if( ptree->pBrother== NULL )


    思路:选F。


    P19(多选)
    数组aaNode中NULL的意义为

    A: 当前节点不再有儿子
    B:当前节点不再有兄弟
    C:当前节点的内容为空
    D:当前节点既没有儿子也没有兄弟
    E:当前节点肯定有儿子
    F:当前节点肯定有兄弟


    思路:选AB。


    P20(多选)
    关于本程序中使用递归方法和堆栈方法进行树遍历,以上叙述正确的是

    A:就算法复杂性而言,递归方法是O(n),堆栈方法是O(nLogn)
    B:就算法复杂性而言,递归方法是O(nLogn),堆栈方法是O(n)
    C:就算法复杂性而言,递归方法和堆栈方法是一样的
    D:递归方法中,需要为局部变量和参数多次分配调用栈,可能导致调用栈溢出
    E:堆栈方法需要进行额外的操作,而且程序复杂,所以速度较慢


    思路:选BD。


    以下问题P21-P25基于下列程序
    一个相连的区域被不规则的分割成n个不同的小区域,每个小区域与若干个其他小区域相临界。现用cn种不同的颜色为该区域着色,要求每个小区域着一种颜色,相邻小区域着不相同颜色。
    设小区域被顺序编号为0,1,...n-1。每个小区域与其他小区域的邻接关系用二维数组bordering表示,元素bordering[i][j]表示i号小区域与j号小区域之间的邻接关系:
    bordering[i][j]=0; j小区域与i小区域不邻接
    bordering[i][j]=1;  j小区域与i小区域相邻接
    程序中,把计算结果存放于二维数组colored中,颜色编号为0,1,...cn-1,元素colored[color][j]的含义是:
    colored[color][j]=0; j小区域不用颜色color着色
    colored[color][j]=1; j小区域用颜色color着色
    函数colorcountry(bordering,colored,n,cn)根据所给的小区域邻接关系数组bordering、小区域个数n,颜色数cn,将找到的着色方案记录在数组colored中,函数采用试探法找出,首先从第一个小区域看第一种颜色开始顺序为每个小区域找着色方案,对某个小区域,当为他找到一种未被他的相邻小区域着色的颜色时,就用该颜色对该小区域着色,并准备处理下一个小区域。当不能为某个小区域找到一个未被他的相邻的小区域着色的颜色时,就回溯。如果最终为全部小区域找到着色方案,函数返回1,否则,返回0,程序假定小区域个数不超过20,颜色数是4。


    //程序
    #include <stdio.h>
    #define N  20
    #define CN  4


    int colorcountry( int bordering[][N], int colored[][N], int n, int cn )
    {
    int color,used,i,c;
    for( color=0; color<cn; color++ )
    /* 设置所有区域未着色*/
    for( i=0; i<n; i++ )
    colored[color][i]=0;
    c=0; // 从第一个小区域开始
    color=0;  // 从第一种颜色开始试探
    while( c<n )
    { // 还未对全部小区域着色时循环
    while( P21 ) // 顺序对每种颜色做试探
    { // 检查当前颜色是否已被某相邻小区域着色
    for(i=0,used=0; !used&&i<c; i++ )
    if( P22 ) used = 1;
    if( !used )
    break;  // 当前颜色未被相邻小区域着色
    color++;
    }
    if( !used )
    { // 找到可用颜色,用此色着色,并准备处理下一小区域
    P23;
    color=0;
    }
    else
    {
    // 未找到一种可用颜色,回溯
    c--;
    if( c < 0 )
    return 0;  // 发现没有解
    for(color=0; P24; color++)
    P25;
    }
    }
    return 1;
    }
    void print( int colored[][N],int n,int cn) // 打印结果
    {
    char* colortbl[] = {"Red","Blue","Green","Yellow"};
    int color,i;
    for( color=0; color<cn; color++ )
    {
    printf(" %s: ", colortbl[color]);
    for( i=0; i<n; i++)
    ff(colored[color][i])
    printf(" %d",i);
    printf(" ");
    }
    }
    int colored[CN][N],bordering[N][N];
    void main()
    {
    int i,j,n;
    printf("enter number of areas");
    scanf("%d",&n);
    printf("enter bordering: ");
    for(i=0; i<n; i++ )
    for(j=0;j<n;j++ )
    bordering[i][j]=0;
    for(i=0;i<n;i++)
    {
    printf("enter areas to link %d area(<0 to next) ",i );
    scanf("%d",&j);
    while( j>=0 )
    {
    if( i!= j )
    bordering[i][j]=bordering[j][i]=1;
    scanf( "%d", &j );
    }
    }
    if( colorcountry( bordering, colored, n, cn ) )
    print(colored,n,cn);
    else
    printf("no solution. ");
    }
    //问题(P21-P25都是单选)
    P21:

    A: color<=cn
    B: color<cn
    C: color++<=cn
    D: color++<cn


    思路:这里的判断条件是颜色的遍历,选B。


    P22:
    A:bordering[c++][i]== 1 && colored[color][i] == 1
    B:bordering[c+1][i]== 1 && colored[color][i] == 1
    C:*(bordering[c+1]+i}== 1 && colored[color][i] == 1
    D:bordering[c][i]== 1 && colored[color][i] == 1


    思路:判断着色数组和邻接数组,后半部分都是一样的,前半部分,只需要以单行为目标,看它相邻的区域就可以了,bordering行号固定,选D。


    P23:
    A:colored[color][c]=1
    B:colored[color][c+1]=1
    C:colored[color][++c]=1
    D:colored[color][c++]=1


    思路:给目标区域着色,c自增,开始针对下一行(下一个区域),选D。


    P24:
    A:colored[color][c] == 0
    B:colored[color][c]
    C:colored[color+1][c]==0
    D:colored[color+1][c]


    思路:回溯开始的时候,把找不到方案的上一个区域着色废除,遍历的条件就是找到这一行里有着色过的,选B。


    P25:
    A:colored[color+1][c]=0
    B:colored[color-1][c]=0
    C:colored[color][c]=0
    D:colored[color++][c]=0


    思路:接着上一题,找到着色的记录,就重置为0,选C。


    本程序是一个简单的计数器模拟程序。对任何的正确四则运算表达式,程序计算器结果展开输出。表达式中运算分量为无正负号整数。运算符为+、-、*、/,圆括号按常规配对,表达式以字符‘=’结束。
    函数gettach()为获取表达式的一个合法字符,并将字符存入变量curch,函数指针数组func[]是为了统一加减乘除计算而设置的。
    // 程序
    #include<stdio.h>
    #include<string.h>


    int add(int x,int y) { return x + y; }
    int sub(int x, int y) { return x - y; }
    int mul(int x, int y) { return x*y; }
    int div(int x, int y) { return x/y; }
    int (*func[])(int x, int y) = { add, sub, mul, div };
    int num, curch;
    char chtbl[] = "+-*/()=";
    char corch[] = "+-*/()=0123456789";
    int getach()
    {
    unsigned int i;
    while( true )
    {
    curch=getchar();
    if( curch=eof )
    return -1;
    for( i=0; corch[i]&&curch!=corch[i]; i++ );
    if( i < strlen(corch) )
    break;
    }
    return curch;
    }
    int getid()
    {
    int i;
    if( curch>= '0' && curch<= '9' )
    {
    for(num=0; curch>='0' && curch<='9'; getach() )
    num= P26;
    return -1;
    }
    else
    {
    for(i=0; chtbl[i]; i++ )
    if(chtbl[i] == curch)
    break;
    if( i <= 5 )
    getach();
    return i;
    }
    }
    int cal()
    {
    int x1,x2,x3,op1,op2,i;
    i=getid();
    if( i== 4 )
    x1 = cal();
    else
    x1= num;
    op1=getid();
    if(op1>=5)
    return x1;
    i= getid();
    if( i == 4 )
    x2 = cal();
    else
    x2 = num;
    op2 = getid();
    while( P27 )
    {
    i=getid();
    if( i==4 )
    x3 = cal();
    else
    x3 = num;
    if( (op1/2==0) && (op2/2==1) )
    x2 = func[op2](x2,x3);
    else
    {
    x1 =  P28;
    x2 = x3;
    P29;
    }
    op2 = getid();
    }
    return P30;
    }


    void  main()
    {
    int value;
    printf("Please input an expression: ");
    getach();
    while( curch != '=' )
    {
    value = cal();
    printf( "the result is :%d ", value );
    printf( "please input an expression: ");
    getach();
    }
    }


    //问题(P26-P30都是单选)
    P26:

    A:(num+1)*10+curch-1-'0'
    B:(num+1)*10+curch-'0'
    C:num*10+curch-'0'
    D:num*10+curch-1-'0'


    思路:这里收集输入数字,按位逐个获取,先获取到的随着位数增加,每个乘10,选C。


    P27:
    A:(op2>=0)&&(op2<6)
    B:(op2<6)
    C:(op2>=0)&&(op2<5)
    D:op2>=0


    思路:这里判断op2操作符,如果是‘=’,’)‘这种的就直接计算x1与x2的结果,不走while循环直接返回,如果是“+-*/”这种,说明还有计算的余地,需要while循环,选C。


    P28:
    A:func[op2](x1,x2)
    B:(*func[op2])(x1,x2)
    C:(*func[op1])(x2,x2)
    D:func[op1](x1,x2)


    思路:这个else区域对应的if判断意思是:如果x2与x3之间操作符是乘除的,x1与x2之间是加减的,那么先算x2与x3,结果视为x2,;
    对应的,不考虑这种运算符优先,就是从左到右迭代的先算x1与x2,操作符是op1,func在声明时已经是函数指针变量,直接调用,选D。


    P29:
    A:op2=op1
    B:op1=op2
    C:x3=func[op1](x1,x2)
    D:x3=(*func[op1])(x1,x2)


    思路:接着上一题,x3在迭代之后变成了x2,那么op2运算符也就成了op1,选B。


    P30:
    A:func[op2](x1,x2)
    B:(*func[op2])(x1,x2)
    C:(*func[op1])(x1,x2)
    D:func[op1](x1,x2)


    思路:由上面的迭代方法可以看出,无论是多个运算数,都需要变成 x1 op1 x2 的形式,如果不走while循环,直接看出对x1和x2操作就够了,选D。

  • 相关阅读:
    APB协议
    AHB总线协议(一)
    C++内存机制中内存溢出、内存泄露、内存越界和栈溢出的区别和联系
    深入理解C++内存管理机制
    c/c++内存机制(一)(转)
    与临时对象的斗争(下)
    与临时对象的斗争(上)ZZ
    C++异常处理解析: 异常的引发(throw), 捕获(try catch)、异常安全
    qt5信息提示框QMessageBox用法
    红黑树
  • 原文地址:https://www.cnblogs.com/to-creat/p/5547691.html
Copyright © 2020-2023  润新知