C语言面试题整理

1.什么是预编译?

        预编译又称预处理,是整个编译最先做的工作,及程序执行前的一些预处理工作

        主要处理开头的指令,如拷贝  #include包含的文件代码、替换#define定义的宏、条件编译等

        何时需要预编译? 总是使用并不经常改动的大型代码体

2. # 和 ##的作用?

        #是宏参数 字符串替换运算符,## 是把两个 宏参数 连接起来的运算符

        #define   STR(arg)        #arg      //STR(hello)     ---> hello 

        #define  NAME(x,y)       x##y        //NAME(1,2)  ---->12

3.static 的用法

        修饰局部变量,该变量(栈区)变成静态存储类型,该变量存在于内存的静态区,作用域不变。

        修饰全局变量,该变量的作用域,只能在该文件中使用。不能在其他文件中通过extern引用

        修饰函数,        静态函数只能在申明它的源文件中使用

4. extern 关键字的作用

        用来修饰变量或函数,表明该变量或函数都是在别的文件中定义的,提醒编译器在其他文件中寻找定义

5. sizeof 的用法

        计算对象或类型所占的内存字节数

        sizeof 求得的结构体(及其对象)的大小,不等于各个数据成员大小之和

sizeof运算符 和函数strlen的区别与用法

        sizeof()计算分配空间的字节数

        strlen()计算空间中的字符个数(不包括'\0')

        char str[20] = {"hello world"};

        int   a[10];

        sizeof(str);      //20              sizeof(a);     //4*10        strlen(str);        //11

6.结构体的赋值

        C语言中对结构体变量的赋值##在初始化##或者在定义后按字段赋值

       1. struct   dog{    char   a;  int   b;  }x={ 'a' ,2 };

        2.定义结构体变量         struct dog  x  ={ 'a' ,2 };

        按字段赋值

        struct  dog  x;    x.a='a';     x.b =2;

7.结构体变量如何比较?

        int  memcmp( const void *s1, const void *s2, size_t  n)        //n 为比较长度

        按字节来比较,如果超过数组长度,比较不会停止'\0' 之后继续比较下去,可能会出错

        strcmp()        按字符比较,直到出现结果

结构体成员数组大小为0,好处是可以在结构体中分配不定长的大小

typedef  struct  st{

        int   a;

        int   b;

        char c[0];        //空数组

} st_t;

        sizeof(st_t)  == 8; //即 c[0] ==0

8. malloc/free 与new/delete 的区别

        1.malloc/free 是C/C++语言的标准库函数,new/delete 是C++的运算符,都是动态分配和释放内存。2.主要区别就是 new/delete 对象在创建和释放的时候会执行构造函数和析构函数

9.C++里的类 和C里面的 struct 区别

        C++     中的类具有成员保护功能,并且具有继承和多态的特点

        C里面没有成员函数,可以有函数指针,不能继承和派生,但可以结构体嵌套

10.主函数的写法

        int main(void)  {} 

        int  main( int argc, char  *args[] ){}

        argc  运行程序执行的参数个数 , 第一个是程序本身   xx.out

        args   指向字符串数组的指针   xx.out  ...

11.printf();

        int   a=10, b=20, c=30; 

        printf("%d  %d   %d\n",a+b+c, (b=b*2), (c=c*2) );  //  110  40  60   输出从右往左

        float   double 的输出   %.2f  --- 输出两位小数,会自动进行四舍五入

                     %8.2f --- 8是输出宽度,就是数据输出后在终端上占据8个 英文字母,不足的补空格

                float  a= 13.141592f;

                printf("%d\n", a );    //输出随机数,从内存里随机取来输出,不进行类型转换

12. float  x 与 "零值 "的比较

        const   float  ESP  = 0.000001f;  //参考量

        float  x  = 0.0f;

       if( x >= -ESP &&  x <= ESP ){  yes  } else { no  }

        指针  int   *p =NULL;

               if(p == NULL ){}

13.操作符优先级问题

        char  *ptr  = "Linux";        //在内存中占了 6个字节 '\0'

        printf( "%c\n", *ptr++ );   // L 同一级,自右向左      ++*ptr ==M 编译报错,L常量不允许修改

        printf("%c\n", *ptr);         // i

        书写时多加(),

        伪运算符 优先级最高()

        单目大于双目  三目

        算符运算符 >  比较运算符 > 位运算符  > 逻辑运算符

自右向左:只有单目(++ --)  三目  赋值运算符

        三目运算: a?b:c?d:e  就是  a?b : (c?d:e )     //运算从左向右,结合从右向左

14.逻辑短路问题 : 有逻辑运算参与    

     int a=5,b=6,c=7,d=8,m=2,n=2;
    (m=ad);    //m=1  ||或 为真所以右边的不会执行  n的值不变
    printf("%d\t%d\n",m,n);   //1  2
    
    (m=ad);   //true && fause  m=1 n=0
    
    (n=c>d)||(m=a

    (n=c>d)&&(m=a

     应用:
    if(strlen(name)!=0 && strcmp(name,"admin") ==0 ){}  保证了程序的健壮性
    //name ==NULL 后面就不会执行
    
    递归终结条件
    int test(int n,int *sum)
    {
        n && test(n-1,sum);
        return *sum += n;
    }
    
    int ret =0;
    test(100,&ret);   //1+2+3.....+100
    
如果说不能使用循环,那么第一个应该想到 递归 重视逻辑短路问题

15.分支
    int a=4;
    switch( a>5 ){ case 0: ...}   //a>5 判断表达式  假0  case 0会被执行
        switch( 3.14 )   //报错  不能放 float double
        //只能放整型(数)   ---char bool short long 枚举
        对于switch 控制表达式应该是整数类型,case也一样
        并且 case表达式转换后的值,不应该是相同的值 'a' ==97
        
    switch(a-4){  //2  普通表达式
        case 0:   ; break;
        case 1:   ;  break;
        
        case 2:   ;  break;  
        
        default:  //other 
            printf("");
    }
   16.二维数组和一维数组的相互转化

        将一维数组中的元素分别存入二维数组中

    int a[9] ={1,2,3,4,5,6,7,8,9};   
        int b[3][3];
    (1).代码实现:
        int i, j, k=0;
        for(i=0; i<3; i++){  //控制行
            for(j=0; j<3; j++){  //控制列
                b[i][j]=a[k]; k++;
            }
        }
    (2).借助边界
        for(i=0; i<9; i++){   //控制数组a  
            b[0][i] = a[i];   //赋值 
        }
    (3).运算:
        for(i=0; i<9; i++){
            b[i/3][i%3] =a[i];
        }
        

        二维数组转化为一维数组:
        int array1[A][B] ={1,2,3,4};   int array2[A*B];
        for(i=0; i             for(j=0; j                 array2[k] = array1[i][j];  //将二维数组的值一个个存入一维数组中
                k++;
            }
        }
17.字符串

strcpy() 和memcpy()的区别??

        strcpy()  字符串拷贝,自动拷贝一个'\0'

        memcpy(char *dst, const char *src,  size_t  len );  内存拷贝

        区别:

                strcpy()只能使用在字符串上,会自动拷贝'\0'

                memcpy() 适用于各种类型,必须要指定长度

char  name[10];

gets(name);                 //当 name length ,超过10,内存越界,可能出错

18.指针

        char a;        //开辟一个字节的空间

        char  *str =&a;

        strcpy(str, "hello");           //6 个字节的空间,越界 可能导致程序崩溃

内存分区:
        栈区:函数参数 局部变量 系统管理(释放 回收)
        
        堆区:程序员管理(malloc/free)作用域 生命周期
        
        *静态存储区:全局变量 静态数据(static) 常量
            bss段:未初始化的全局变量、静态变量
            data段:初始化全局变量  静态变量  常量 
        
        代码区:程序代码

int  main()

{

char  *str = "abcd";   //str--栈区   "zbcd"--静态数据区-常量  不允许更改

str[2] = 'f';        //程序崩溃  段错误--试图去修改不属于你的内存空间

}

int  *p;---->malloc(p); ----->free() ----->p= NULL;

自己开辟的空间free之后一定要记得置空---清理

数组和指针相结合?/

        int  a[5] = {1,2,3,4,5};

        int   *ptr =(int *)(&a+1);   //&a[0] --取的是整个数组,指向数组尾部

        printf("%d  %d\n", *(a+1), *(ptr-1) );        //2, 5

        a[0] 第一个元素的首地址,  第一个字节 ,用首地址来表示整个数组

19.不定长函数参数

        (传参1,传参2,...)

        printf(const char *format, ...);    //也是一个不同的函数,可以传不同的参数,打印出来

                //  ...  可以传任意长度的参数---变长参数

        va_start(va_list arg_ptr ,prev_param);
         //以固定参数的地址作为起点,确定变长参数的内存起始地址,获取第一个变长参数的首地址
    
            va_arg(va_list arg_ptr,type);  //获取每个变长参数  返回的参数类型
            va_end(va_list arg_ptr);  //将va_list 类型的arg_ptr置0
    

20.const    不可改变的变量

        char   const  *p1;

        const  char   *p2;    //p1 and  p2可以指向不同的空间,指向的变量的内容不能改变

        char  *const  p3;    //不可以指向其它空间,可以修改里面的内容

21.结构体

        //#pragma    pack(n)   //宏定义 指定对齐模数n  必须是2的整数倍

        字节对齐

        第一原则:

        对齐模数     32位机器---4字节     64位机器---8字节

        第二原则:

              按照对齐模数和  结构体中最大的数据类型大小比较 ,以比较小的那个数据大小进行对齐

        第三原则:

                结构体中第一个数据放在 offset 为0的位置,每个数据,存储的起始位置,必须是自身大小的整数倍

	struct {
		short a;
		short b;
		short c;
	}C;     sizeof(C) ==6;
	
	struct {
		char a;
		short b;   //2
		int c;  //1*11 1111          
	}d;    sizeof(d)==8;   四字节对齐 cpu取数据 一次取四个字节(char short) 
	
	struct {
		char a;
		int c;  //1*** 1111 11** 
		short b;   //2         
	}f;   //12    四字节对齐 cpu取数据 int要取两次 ,被拆开不符合规则  

	struct {
		char a;
		short b;
		char c;
		int d;  //4
	}e;    //sizeof(e) ==12
	

22.联合体和大小端问题   

union xx{        //联合体整个空间是 最大成员在空间中的大小
    short a;
    char b[sizeof(a) ];   //short 在不同的编译器中所占的字节不同
} kk;

if(sizeof(a) ==2){
    kk.a =0x1234;   //看这个数据字这片空间 里是怎样存储的就知道大小端
    if(kk.b[0] ==0x12 ){
        printf("big");  //高字节存储在低地址
    }else {
        printf("small");
    }
}

23.C语言文件操作

        文件拷贝,实现一个函数

int  copy_file(const char *srcFilename, const char desFilename)
{
    //打开文件 r r+  rw  w+
    FILE *fp =fopen(srcFilename , "r");
    if(fp<0){
        printf("%s没有打开成功\n",srcFilename);
        return -1;
    }
   FILE *fp1 =fopen(desFilename , "w");
    if(fp<0){
        printf("%s没有打开成功\n",desFilename);
        return -1;
    }
    /*
        fgetc()        fputc()
        fgets()        fputs()
        fscanf()       fprintf()  格式化写
        fread()        fwrite()   二进制写
    */ 
    char ch = '\0';
    while(EOF != (ch =fgetc(fp) )){  //文件结束 =EOF
        fputc(ch, fp1);
    }  
        
    fclose(fp);  fclose(fp1);
    fp =NULL;  fp1=NULL;

  
    return 0;
}

24.代码改错题

int main(int argc, char *argv[] )
{
	char *ptr = (char *)malloc(10);
	if(NULL ==ptr ){
		printf("\nmalloc failed\n");
		exit(1);			//return -1;
	}else if(argc ==1 ){
		printf("\n Usage \n");
	}else{
		memset(ptr, '\n', 10);
		strnpy(ptr, argv[1],9);
		while( *ptr != 'z'){
			if(*ptr ==' ')  break;
			else ptr++;   //指针的指向改变了
		}
		if(*ptr =='z'){
			printf("stream contained 'z'\n");
		}
		free(ptr);  //zhhgg--没问题 freez释放不属于自己的空间 ,段错误 
	}
}

25.exit() 的用法

exit(num);  
    
    根据 num的值,知道程序退出的原因
    num =0 正常退出
    !0     不正常退出 
    
    int main(int argc, char *argv[])
    {
        if(argc ==1){
            exit(1);
        }else{
            exit(0);
        }
        return 0;
    }
    脚本:exit.sh 
    gcc exit.c -o exit 
    ./exit
    echo $?
    ./exit asd
    echo $?    ## $?程序退出的返回码
    
    system("touch aa");  //终端创建一个文件 aa 
    
    system("rm -rvf aa");  //删除 aa 
    
    atexit(func);   //func 函数名/函数指针 --当退出的时候
    每次调用 exit();的时候,都会去调用这个传入的函数func
        一般用来作为程序结束前的清理工作
    注:
        传入的函数,必须是无参数的
        可以传入32个,调用的顺序和传入的顺序相反
        
        _exit(1)     运行到该函数,直接退出程序 ,并不会执行绑定的函数

26.函数返回值

int fun(int val)
{
    int a =val;
    a++;
    return &a;   //局部变量的地址,函数结束,这片内存就销毁了
}

int main()
{
    int a = 10;
    int *val = fun(a);   //可能会出现段错误 
}
   // 多文件编程 全局变量(同名变量)不可以定义在多个.c 文件中
    //static 修饰 变成静态变量,模块化只能在本文件中使用
    
    作用域大的值和作用域小的值同名,则会使用作用域小的值
27.    代码改错题

void  strcopy(void)
{
    char *src = "hello world";
	char *dest =NULL;
	int len =strlen(src);  //不包含'\0',计算的是有效字符的长度
	dest = (char *)malloc(len+1);  //多开辟一个字节大小的空间  malloc(len)
	char *d = dest;
	char *s = &src[len-1];  //指向'\0' 前的一个字符(有效字符) src[len]
	// '\0'字符串的结束标志
	while(len-- != 0)
	{
		*d++ = *s--;  //直接指针赋值不允许  d++ = s--;
	}
	*d = '\0';
	free(dest);
	dest =NULL;

}

    28.将小写字母转换成大写字母

void upperCase(char str[] )
{
	int i=0;     //sizeof(str)==8 ---指针大小的长度
	//越界  
	for(i=0; i

29.内存分配和二级指针

堆 heap  ---(空闲表)--有空间就可以申请,使用完free就放回这个--空闲表 
栈 stack

char *p1;
int main()
{
    char s[] = "abc";  //栈区--数组 
    char *p = "123456";   //p 栈区  "123456" 常量数据区
    
    p1 = (char*)malloc(10);  //堆区 
    strcpy(p1, "123456");   //常量数据区
}

二级指针:指向指针的指针
    如何在函数中更改一级指针呢?
        传二级指针
    void change(int **a, int **b)
    {
        *a= *b;   //更改一级指针
    }
        
    int main()
    {
        int a=5,b=6;
        int *c= &a,*d = &b;
        change(&c, &d);
        printf("%d\n", *c);  //6
        
    }

30.联合(共用)体

联合体的大小--取决于它所有的成员中占用空间最大的一个成员的大小
    
union u1{
    double a;
    int b;
};    //8

union u2{
    char a[13];
    int b;   
};    //16   ---int 四字节对齐

union u3{
    char a[13];
    char b;
};    //13  一字节对齐

不仅仅union,对于别的复合数据类型,如union,struct ,class 的对齐方式为成员中最大的成员--对齐方式

可以使用 #pragma pack(x),可以改变编译器的对齐方式

31.int (*a[10])(int) 的含义

        从右向左,由远及近,括号优先

        a是一个数组,里面存放的是指针,所存的指针是指向有一个int形参的函数,函数的返回值是int型。

32.如何让程序跳转到 0地址执行?

        C语言程序跳转需要调用函数 /函数指针

        typedef  void (*Fun_t)(void)

        Fun_t  fun =(Fun_t)0;     //初始化函数指针

        fun();

33.带参宏和带参函数的区别

        主要是看它们处理的时间,参数类型,程序长度,运行时间。

        带参宏,编译时展开,没有参数类型,程序变长,不占运行时间

        带参函数,运行时处理,参数类型需定义,函数调用和返回时耗费时间

34.volitile 的用法--易变的

        修饰变量,告诉编译器不要缓存到寄存器,每次直接读取该地址

        用途:1.并行设备的硬件寄存器 (如:状态寄存器)
                2.一个中断服务子程序中会访问到非自动变量
                3.多进程中会被几个任务 共享的变量
  


         这是我自动总结的,难免会有出错的 

如果有什么不对的,欢迎在评论区留言

你可能感兴趣的:(c语言,开发语言)