2015年纳新面试题

《第一题》考点:大小端问题
大端:高位存放在低地址,低位存放在高地址(即”高低低高”)。
小端:高位存放在高地址,低位存放在低地址(即”高高低低”)。
例如:
0x0000 0x12
0x0001 0x34
0x0003 0xab
0x0004 0xcd
若字节序为Big_Endian模式,则读出的是:0x1234abcd。
若字节序为Litter_Endian模式,则读出的是:0xcdab3412。

// 第一题题目
#include <stdio.h>

int main(int argc, char *argv[])
{
    int c;

    memcpy(&c, "linux", 4);
    /* 
     * C和C++使用的内存拷贝函数,memcpy函数的功能是
     * 从源src所指的内存地址的起始位置开始拷贝n个字节
     * 到目标dest所指的内存地址的起始位置中。
     * /
    printf("%d\n", c);

    return 0;
}

《第二题》考点:关键字static的用法
我们在这里主要说的是在C语言中的用法。
一. 静态局部变量
1. 静态变量存放在内存中中的静态存储区中,所占用的存储单元不释放,直到整个程序运行结束。
2. 静态局部变量的初始化只在编译时进行一次,程序运行过程中不再进行初始化。
3. 静态局部变量定义时如果没有赋初值,系统编译时,会自动给其赋初值。数值型初始化为0,字符型初始化为NULL。
4. 静态局部变量仅仅在定义它的函数中可以使用,其他函数不能使用。
二. 静态全局变量
1. 作用域即从定义处开始到本文件结束。
2. 仅仅可以在本文件中进行访问,禁止在其他文件中访问。
3. 生存期为整个程序的运行过程。

#include <stdio.h>

int *func()
{
    static int a = 1;
    a++;
    return &a;
}
int main(int argc, char *argv[])
{
    int *b;

    b = func();
    printf("%d\n", *b);
    // 输出:2
    b = func();
    printf("%d\n", *b);
    // 输出:3
    return 0;
}

《第三题》sizeof与strlen
简单介绍下sizeof:
功能:sizeof是一个运算符,不是一个函数。作用在于获得一个对象或类型所占的内存字节数。
语法:
sizeof( 对象 );
sizeof( 类型 );
简单介绍下strlen:
功能:strlen是一个函数。作用在于计算字符串str的长度,不含有’\0’.
语法:
strlen( char * str ) ;

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

void func ( char * a ) {
    printf ( "%d\n" , sizeof ( a ) ) ;
    //数组退化为指针,和一个int类型的大小相同 
    //因此64位系统输出8;32位系统输出4
    printf ( "%d\n" , strlen ( a ) ) ;
    // 字符串实际长度:11
}

int main ( void ) 
{
    char a[]="Hello World" ;

    func ( a ) ;

    printf ( "%d\n" , sizeof ( a ) ) ;
    // 数组a所占的内存大小(包含'\0'):12
    printf ( "%d\n" , strlen ( a ) ) ;
    // 字符串的实际长度:11
    return 0 ;
}

指针的大小是问:一个指针变量占用多少内存空间?
分析:既然指针只是要存储另一个变量的地址,。注意,是存放一变量的地址,而不是存放一个变量本身,所以,不管指针指向什么类型的变量,它的大小总是固定的:只要能放得下一个地址就行!

存放一个地址需要几个字节?答案是和一个 int 类型的大小相同。

《第十三题》

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

int main ( void )
{
    printf ( "%s\n" , "\0\0\0\0\0" ) ;
    char *str1 = "WelcomeTo\0XiyouLinux\n" ;
    char str2[] = "WelcomeTo\0XiyouLinux\n" ;

    printf ( "%d\n" , printf ( "%s\n" , str2 ) ) ;
    // 考查'\0'为字符串的结束标志,同时考查printf的返回值.
    /* printf返回值为输出字符的个数,如果出错返回负数.*/

    printf ( "%d , %d\n" , strlen ( str1 ) , strlen ( str2 ) ) ;
    // str1与str2字符串的有效输出长度均为9,即WelcomeTo。
    printf ( "%d , %d\n" , sizeof ( str1 ) , sizeof ( str2 ) ) ;
    // str1是一个指针,它的大小与该机器的int字节数相同,即8.
    // str2是一个字符数组,它的大小为所占的内存空间大小,即22*1.

    return 0 ;
}

《第四题》考点:数据类型转换。
如果一个运算符两边的运算属类型不同,先要将其转换为相同类型,即较低类型转换为较高类型,然后在参加运算。转换规则如下:
double <—- float 高

long

unsigned

int <—- char,short 低
横向箭头表示必须的转换,即两个float仍然要先转换成double类型再进行计算,结果仍然为double。纵向箭头,则是在运算符两边类型不同时进行转化。

赋值类型的转换:
    将赋值运算符右侧表达式的类型转换为左侧变量的类型。
#include <stdio.h>

void func ( void )
{
    unsigned int a = 6 ;

    int b = -20 ;

    printf ( "%d %u\n" , a+b , a+b ) ;

    ( a+b>6 ) ? puts( ">6" ) : puts ( "<6" ) ;
    // >6 考查:类型自动转换
}

int main ( void )
{
    func () ;

    return 0 ;
}

输出:>6。
原因:当表达式中出现有符号型和无符号型时,有符号类型操作数自动转换为无符号类型。因此-20变成了一个正整数,所以表达式的计算结果大于6。

《第五题》考点:编译预处理
预处理一般是指在程序源代码被翻译为目标代码的过程中生成二进制代码之前的过程。C编译系统在对程序进行通常的编译之前,先进行预处理。C提供的预处理功能主要有一下三种:
1. 宏定义
2. 文件包含
3. 条件编译

《第八题》考点:宏定义

#include <stdio.h>

#define f(a,b) a##b
#define g(a) #a
#define h(a) g(a)

int main ( void )
{
    printf ( "%s\n" , h(f(1,2)) ) ;
    // 12
    printf ( "%s\n" , g(f(1,2)) ) ;
    // f(1,2)
    return  0 ; 
}
“#”代表和一个字符串相连接(#的功能是将其后面的宏参数进行字符串化操作)
“##”代表是和一个符号相连,符号可以是变量,或者是另一个宏符号(##的功能是链接的作用)
只要出现了#号那么里面的f(1,2)直接被转化成“f(1,2)” 所以就不会再进行替换了

《第六题》考点:数组与指针

// 第六题
#include <stdio.h>

int main ( void )
{
    int a[3][4] ;

    printf ( "%p\n%p\n%p\n%p\n%p\n%p\n" , &a[0][0] , a[0] , a , a[0]+1 , a+1 , a[1] ) ;
    // %pprintf函数中,表示以十六进制输出指针或地址。

    return 0 ;
}

输出结果:
0x00000000 0x00000000 0x00000000 0x00000004 0x00000020 0x00000020

《第十二题》

#include <stdio.h>

int main ( void )
{
    int a[5] = { 1 , 2 , 3, 4 , 5 } ;
    int * ptr = ( int * ) ( &a + 1 ) ;

    printf ( "%d , %d\n" , *(a+1) , *(ptr-1) ) ;
    // 输出结果:2 , 5
    // 原因:a,&a的地址是一样的,但意思不一样,a是数组首地址,也就是a[0]的地址,&a是对象(数组)首地址,a+1是数组下一元素的地址,即a[1],&a+1是下一个对象的地址,即a[5].

    return  0;
}

输出结果:2 , 5
分析题目:a , &a的地址是一样的,但意思不一样,a是数组首地址,也就是a[0]的地址,&a是对象(数组)首地址,a+1是数组下一元素的地址,即a[1],&a+1是下一个对象的地址,即a[5].

《第七题》考点:结构体内存对齐

#include <stdio.h>

struct first {
    int x ;
    char y ;
    double z ;
} ;

struct second {
    int x ; 
    double y ;
    char z ;
} ;

int main ( void )
{
    printf ( "%d %d\n" , sizeof ( struct first ) , sizeof ( struct second ) ) ;
    // 16 = 8*2  24 = 8*3 
    return  0;
}

《第九题》考点:switch case语句

#include <stdio.h>

int main ( void )
{
    int i ;

    scanf ( "%d" , &i ) ;

    switch ( i ) {
        case 1 :    printf ( "I'm case 1.\n" ) ; break ;
        default :   printf ( "I'm default.\n" ) ;   
        case 2 :    printf ( "I'm case 2.\n" ) ;    break ;
    }

    return 0 ;
}
输出结果:
    i=1时只输出第一句,原因是满足i=1,所以选择case1,同时因为break所以后续的情况不会执行。
    i=2时只输出第三句,因为其是switch结构的尾巴。
    i=3时输出后两句,原因是找不到相匹配的case情况。因此执行default下的语句,由于它没有break语句,因此还是会执行后面的语句,故输出了后两句。

关于switch后的括号里面的变量类型要求:
    一般使用的类型是整型,字符型;浮点型是不可以的,因为在计算机存储中会有精度的损失。所以不可以使用浮点型数据。

《第十题》考点:const与指针的结合

#include <stdio.h>
/* 解释这三个指针的区别。*/
int main ( void )
{
    int a = 3 ;
    int b = 9 ;

    const int *p1 ;
    p1 = &a ;
 // *p1 = 3 ;
    p1 = &b ;

    int const *p2 ;
    p2 = &a ;
    p2 = &b ;
 // *p2 = 3 ;

    int * const p3 = &a ;
 // p3 = &b ;
    *p3 = 9 ;

    return 0 ;
}
前两种是一样的:
    定义指向const的指针(指针指向的内容不能被修改)即程序中的注释语句取消注释,编译不能通过。
第二种:
    定义const指针(指针的指向不能被修改)即程序中的注释语句取消注释,编译不能通过。 

《第十一题》考点:extern关键字

#include <stdio.h>
// 不允许改变源代码顺序,只可以进行添加操作。
void func ()
{
    // extern int a ;
    printf ( "a = %d\n" , a ) ;
}

int a = 10 ;

int main ( void )
{
    func () ;

    return 0 ;
}

该程序不能编译通过,原因在于:a变量声明定义在func函数后面,所以它的作用域在int a = 10 ;这条语句以下。
因此我们需要在fun函数中可以访问a变量,则需要printf()语句之前添加声明extern int a;当然大家使用传值的方法也可以实现,不过这里我们考察的是大家对extern这个关键字的掌握。

《第十七题》考点:文件的细节操作

#include <stdio.h>

/*  fgets函数用来从文件中读入字符串。fgets函数的调用形式如下:fgets(str,n,fp);
 *  此处,fp是文件指针;str是存放在字符串的起始地址;n是一个int类型变量。
 *  函数的功能是从fp所指文件中读入n-1个字符放入str为起始地址的空间内;如果在未读满n-1
 *  个字符之时,已读到一个换行符或一个EOF(文件结束标志),则结束本次读操作,读入的字符
 *  串中最后包含读到的换行符。因此,确切地说,调用fgets函数时,最多只能读入n-1个字符。
 *  读入结束后,系统将自动在最后加'\0',并以str作为函数值返回。
**/

int main ( void )
{
    // 表示以只读形式打开文件
    FILE *fp = fopen ( "a.txt" , "r" ) ;
    /* FILE * fp = fopen ( "a.txt" , "a+" ) ; */
    // 定义一个字符数组
    char buffer[4096] ;

    // 判断文件是否正确打开
    if ( fp == NULL ) { 
        printf ( "文件不存在。\n" );
        return -1 ;
    }

    fgets ( buffer , sizeof ( buffer ) , fp ) ;


    // 测试代码
    printf ( "%s\n" , buffer ) ;

    // 以只读方式打开,写不进去.
    fprintf ( fp , "%s" , buffer ) ;

    // 关闭文件
    fclose ( fp ) ;

    return 0 ;
}

《第十八题》考点:二分查找

#include <stdio.h>

// 考查理解程序
int compare ( int middle , int key ) {
    if ( 0 > middle-key ) {
        return -1 ;
    } else if ( 0 < middle-key ) {
        return 1 ;
    } else {
        return 0 ;
    }
}

// 二分查找法
int binarySearch ( const int list[] , int key , int length ) {
    int left = 0 , right = length - 1 , middle ;

    while ( left <= right ) {   
        middle = ( right - left ) / 2 + left ;

        switch ( compare ( list[middle] , key ) ) {
            case -1 :   left = middle + 1 ;     break ;
            case 0 :    return middle ;
            case 1 :    right = middle - 1 ;
        }
    }
}

/* 
 * 思考程序为什么可以这样做?可以这样做的前提条件是什么?
 * 首先程序中的数组必须有序存储,这样才可以使用二分查找。
 * 对于middle的计算方法:尽管(right+left)/2与
 * (right-left)/2+left等价,但是后者可以降低相加溢出的可能性。
 * /

假如,left与right之和超过了所在类型的表示范围的话,那么middle就不会得到正确的值.
所以,更稳妥的做法应该是:( right-left )/2+left

《第十九题》考点:函数参数的传递

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

void func ( char ** p )
{
    *p = ( char * )malloc ( sizeof ( char ) ) ;
    printf ( "p = %p\n" , p ) ;
}

// 传值与传址
int main ( int argc , char * argv[] ) {
    char * s = NULL ;
    printf ( "s = %p\n" , s ) ;

    func ( &s ) ;

    printf ( "s = %p\n" , s ) ;

    strcpy ( s , "i love xiyou_linux" ) ;

    puts ( s ) ;

    return 0 ;
}

《第二十题》考点:main函数的形参理解

如何理解本张试卷中所出现的
“int main( int argc, char *argv[] )”?
请谈一谈你所理解的 main 函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值