C语言复习

目录

章节概述:

重点问题:

1. C与C++的区别(即面向过程和面向对象的区别)

2.什么是预编译?何时需要预编译?

3.⭐什么是动态内存分配?动态分配和静态分配的区别是什么?

引入:指针和引用的区别

4.对于函数的参数的传值、传址、引用的区别

5.⭐关键字static的作用

6.struct和class的区别是什么?详述在两种语言中不同

7.break和continue的区别?

8.typedef和#define的区别?

9.指针和数组的对比?

10.谈一下指针数组和数组指针的内存布局?

具体问答:

1.C语言中的运算符优先级关系?

2.谈一下什么是函数,有哪些特点?

3.谈一下C语言程序运行的编译和链接过程

4.谈一下什么是指针?指针有哪些特点?

5.⭐谈一下内存分配和内存释放? 又分为内存的动态静态分配 和 程序的动态静态分配

6.⭐谈一下什么是多级指针?

7.什么是形式参数?

8.谈一下strlen和sizeof的区别?

9.谈一下 #include 和 #include "file.h" 区别?

10.谈一下局部变量能否和全局变量重名?

11.⭐程序的内存分配?

12.谈一下什么时内联函数(inline)?

13.描述一下gcc的编译过程?

14.变量的命名规则

15.函数的定义与函数的声明的区别

16.⭐结构体和共用体的区别?

17.谈一下哪些情况下会出现野指针,如何避免?

18.⭐谈一下如何理解结构体的浅拷贝与深拷贝?

19.谈一下如何理解库函数?

20.定义常量的方法和区别?见问题5

21.C语言的数据类型有哪些?

22.谈一下 i++ 和 ++i 的区别,哪个速度更快?

23.谈一下abs和fabs的区别?

24.谈一下什么是指针的指针?

25.⭐解释一下模块化编程?

26.strcpy 、sprintf 和 memcpy 的区别

27. && 和 & 、|| 和 | 的区别

28.static全局变量和普通的全局变量有什么区别?

29.do...while 和 while 的区别?

30.C语言中的语法


章节概述:

一、基础

基本数据类型

枚举类型

派生类型:指针、数组、共用体、结构体

运算符:位运算符,逗号运算符

常见关键字:

goto语句

二、选择

多分支选择:switch ...case

嵌套选择:else与if最近且没有被匹配的 if 配对

三、循环

while可以一次也不执行,do..while至少执行一次

for循环里循环条件和分号必不可少

四、数组

数值数组:一维、二维

字符数组:字符串和字符串数组

  • 字符串:存储形式、表示形式、处理函数

柔性数组

五、函数

库函数:

自定义函数:

递归

输入输出

六、指针

指针与数组的区别

指针与函数指针的区别

**符号(指针的指针)

*和**的区别

七、结构体和共用体

定义

变量

结构体数组

结构体指针

八、文件编译系统

预处理:编译链接、存储方式、内存分布

动态内存管理:

文件:打开、关闭、定位文件、文件读、文件写


重点问题:

1. C与C++的区别(即面向过程和面向对象的区别)

  • C是面向过程的,C++是面向对象的
  • 面向过程是分析解决问题的步骤,将步骤一步一步实现,使用的时候依次调用即可;面向对象是将问题分解成各个对象,建立对象不是为了完成某个步骤,而是为了描述某个事物在解决相应问题时的行为
  • 面向过程的优缺点:性能比面向对象高,因为类调用是需要实例化,开销比较大;但是没有面向对象易维护、易复用、易扩展
  • 面向对象的优缺点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特点可以设计出低耦合的系统,使系统更灵活、更易维护;但是性能比面向过程低


2.什么是预编译?何时需要预编译?

  • 预编译又称预处理,是整个编译过程最先做的工作,即程序执行前的一些预处理工作。主要处理 以#开头的指令,如#include<>中拷贝包含的文件代码;替换#define定义的宏;条件编译#if等
  • 什么时候需要预编译?
    • 总是使用不经常改动的大型代码体。
    • 程序有多个模块组成,每个模块都是用一组标准的包含文件和相同的编译选项,在此情况下,可以将所有包含文件预编译为一个预编译头。

整个C语言编译流程 

gcc的编译过程

1. 编译预处理

用C编译系统提供的“预处理器”(预处理程序或预编译器)对程序中的预处理指令进行编译预处理。 

预处理指令主要包括以下四个方面:
(1)宏定义指令,#define
(2)条件编译指令,如#ifdef, #ifndef, #else, #elif, #endif
(3)头文件包含指令,如#include “FileName” 或者 #include 等。
(4)特殊符号,预编译程序可以识别一些特殊的符号。

预编译器 完成的是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件,这个文件的意思表达 同 没有经过预处理的源文件是相同的,但实际内容有所不同。这个文件是一个完整的、可以用来进行正式编译的源程序。

2. 编译阶段

经过预编译得到的输出文件中,将只有常量,如数字、字符串、变量的定义,以及C语言的关键字,如main, if, else, for, while, {, }, +, -, *, \, 等等。预编译程序所要做的工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其转化为等价的中间代码表示或汇编代码

3. 汇编过程

汇编过程将上一步的汇编代码转换成机器码(machine code),这一步产生的文件叫做目标文件,是二进制文件

4.链接程序

一个程序可能包含若干个源程序文件,而编译是以源程序文件为对象的,一次编译只能得到与一个源程序文件相对应的目标文件,只是整个程序的一部分,必须把所有的编译后得到的目标文件链接起来,再与函数库相联结成一个整体,生成一个可供计算机执行的目标程序,即可执行程序。

链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来。


3.什么是动态内存分配?动态分配和静态分配的区别是什么?

动态分配内存就是不一次性将整个程序装入主存中。可根据执行的需要,动态的部分装入。同时,已经装入主存的程序不执行时,系统可以回收该程序所占用的主存空间。

C语言通过 malloc / calloc / realloc 函数进行内存申请的 

realloc函数:保存在stdlib.h头文件中

                       void *realloc(void mem_address, unsigned int newsize);

                       函数动态地重新分配先前分配的内存(未释放)并调整其大小

                      指针名=(数据类型)realloc(要改变内存大小的指针名,新的大小)

  • 如果将分配的内存减少,realloc仅仅是改变索引的信息。
  • 如果是将分配的内存扩大,则有以下情况:
    • 1)如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针。
    • 2)如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置。
    • 3)如果申请失败,将返回NULL,此时,原来的指针仍然有效。
#include<iostream>
#include<stdlib.h>

int main(){
	
	int *ptr;
    int i;
    // typecasting pointer to integer
    ptr= (int *)calloc(4,sizeof(int));  
    
    printf("the first allocated memory is %p\n",&(*ptr));
 
    
    if(ptr!=NULL)
    {
        for(i=0;i<4;i++)
        {
                printf("Enter number number %d: ", i+1);
                scanf("%d",(ptr+i));
        }
    }
    //reallocation of 100 elements
    ptr= (int *)realloc(ptr,100*sizeof(int)); 
        
    if(ptr!=NULL)
    {
        printf("\nNew memory allocated!\n");
        printf("the second allocated memory is %p\n" , &(*ptr));
                
    }
    
    free(ptr);
       
    return 0;

	
}

调用结果(1):

the first allocated memory is 0000000000a91420
Enter number number 1: 1
Enter number number 2: 2
Enter number number 3: 3
Enter number number 4: 4

New memory allocated!
the second allocated memory is 0000000000a91420

(此时还有空余的内存空间,就不重新分配新的内存块,直接扩展即可)

调用结果(2):

the first allocated memory is 0000000000871420
Enter number number 1: 1
Enter number number 2: 2
Enter number number 3: 3
Enter number number 4: 4

New memory allocated!
the second allocated memory is 0000000000871910 

(开始只有4个空间,重新分配新的内存空间后有100个空间,这是新的内存地址)

#include<iostream>
#include<stdlib.h>

int main(){
	
	int *ptr,*ptr1;
    int i;
    // typecasting pointer to integer
    ptr= (int *)calloc(4,sizeof(int));  
    
    printf("the first allocated memory is %p\n",&(*ptr));
 
    
    if(ptr!=NULL)
    {
        for(i=0;i<4;i++)
        {
                printf("Enter number number %d: ", i+1);
                scanf("%d",(ptr+i));
        }
    }
    
   //reallocation of 6 elements
    ptr1= (int *)realloc(ptr,1000000000000*sizeof(int)); 
    
    if(ptr1!=NULL)
    {
            printf("\nNew memory allocated!\n");
            printf("the second allocated memory is %p\n" , &(*ptr1));
      
    }else{
    	printf("\nthe allocate is failed\n"); 
	}
	printf("\nthe first pointer still exists\n");
    printf("\nWhat the first pointer points is:\n");
    
    for(i=0;i<4;i++)
    {
            printf("%d    \t",ptr[i]);
    }
    
    free(ptr);
   
    return 0;

	
}

调用结果(3):

the first allocated memory is 0000000000b61420
Enter number number 1: 1
Enter number number 2: 2
Enter number number 3: 3
Enter number number 4: 4

the allocate is failed

the first pointer still exists

What the first pointer points is:
1       2       3       4

(第二次分配的空间太大,没有符合的内存块,分配失败,返回NULL)

注意:如果当前内存段后有足够的空间,realloc()返回原来的指针;

如果当前内存段后没有足够的空间,realloc()返回一个新的内存段的指针


  • 三者的共同点
    1. 都是从堆上进行动态内存分配的(内存分为堆区,栈区,静态区,代码区。 全局变量和静态变量存放在静态区,局部变量存在在栈区,动态申请的变量(即用 new和malloc申请的变量)存放在堆区)
    2. 释放内存都是需要使用free函数的
    3. 三者的返回值都是void *
    4. 都需要强制类型转换
    5. 都需要对申请后的空间判空(如果申请失败 会返回空)
  • 三者的不同点
    1. malloc的参数是需要分配的内存的字节数,函数返回时内存并未以任何方式进行初始化
    2. calloc的参数是需要分配的元素个数和每个元素的长度,函数在返回前把内存初始化为零
    3. 调用realloc函数可以改变一块已经动态分配的内存的大小

与静态内存分配的区别:动态分配不需要预先分配存储空间;并且分配的空间可以根据储层虚的需要扩大或缩小。


引入:指针和引用的区别

 引用,给变量起别名,非法定义:

Point &pt3;
pt3 = pt1;

指针,全称为指针变量,是用来存储内存地址的一种变量。程序中,一般通过指针来访问其指向的内存地址中的内容(数据)

  1. 指针是实实在在的变量,有自己的内存存储空间,它可以指向任何有效的变量。
  2. 引用是一种形式、方法,定义的引用变量,实际上是原实际变量的另一个名称(别名),引用变量本身没有自己的实际存储空间,操作引用变量,就是在操作实际变量。
  3. 底层的实现方法相同,都是按照指针的方式实现的(?)
  4. 引用必须初始化,指针不必;并且引用初始化之后不能改变,指针可以改变所指的对象;不存在指向空值的引用,但是存在指向空值的指针

4.对于函数的参数的传值、传址、引用的区别

  • 传值:(函数参数压栈的是参数的副本),任何的修改都是对副本而言的,没有直接作用于原本的参数上
  • 传地址:(压栈的是指针变量的副本),传地址是传值的一种特殊方式,只是他传递的是地址,不是普通的如int。即把实参的地址复制给形参,复制完毕后实参的地址和形参的地址没有任何联系,对形参的修改不会影响到实参, 但是对形参的值 所指向的对象 的 修改却直接反应在实参中,因为形参的值所指向的对象就是实参的对象。 

传址:等于指针传递,参数本质上是值传递,它所传递的是一个地址值,传值的特点就是不改变本身的值

#include<iostream>
#include<stdlib.h>


void testPointer(int *a){
	printf("指针传递的是 : %p\n", a );
	printf("testPointer函数传入参数的地址:%p\n",&a);
	int b=1; 
	a=&b;
} 


int main(){

	int *b ;
	b = (int *)calloc(1,sizeof(int)) ;
	*b = 1; 
	printf("主函数指针存储的值:%p\n",b);
	testPointer(b);
	printf("由testPointer函数更改后的指针值为:%p\n\n",b);
	return 0;
	
}

主函数指针存储的值:0000000000191440
指针传递的是 : 0000000000191440
testPointer函数传入参数的地址:00000000006ffdf0
由testPointer函数更改后的指针值为:0000000000191440

(传址的被调的函数是不能更改实参本身的值)

  • 传引用:就是让另外一个变量也执行该实参,即两个形参和实参指向同一个对象。对形参的修改,必然反映到实参上
#include<iostream>


void find(int &a){
	printf("引用传递的是 : %d\n", a );
	printf("find函数传入参数的地址:%p\n",&a);
	a = 2;
}


int main(){
	
	int a= 1;
	printf("主函数变量的地址:%p\n",&a);
	find(a);
	printf("又find函数更改后的变量值为:%d",a);
	return 0;
	
}

​​​​​​主函数变量的地址:00000000006ffe1c
引用传递的是 : 1
find函数传入参数的地址:00000000006ffe1c
又find函数更改后的变量值为:2


5.关键字static的作用

  1. 在函数体内,一个被声明为静态的局部变量在这一函数被调用过程中 维持其值不变(并不是数值不能变,如下代码示例)
    1. 在静态存储区分配存储单元,在程序运行的过程中不释放
    2. 在编译时赋初值,实际运行时已经有初值了,每次调用不再赋初值,而是用上次函数调用后的值
      #include<iostream>
      
      void useA(){
      	static int a = 1;
      	a++;
      	printf("%d\t",a);
      }
      
      int main(){
      	
      	useA();
      	useA();
      	return 0;
      }

      2      3

    3. 默认初始化为0。未初始化的全局静态变量和局部静态变量都保存在BBS段(存放程序中未初始化的全局变量的一块内存区域。BSS段属于静态内存分配),BBS段的特点是,程序运行之前会自动清零

                如果是上述代码声明为 static int a; 则运行结果会是 1        2.

  1. 在本文件内(函数体外),一个被声明为静态的全局变量可以被模块内所用函数访问,但不能被模块外其他函数访问,是一个本地的全局变量
  2. 在文件内,一个被声明为静态的函数只可被这一模块内的其他函数调用。即这个static函数被限制在声明它的本地范围内使用

static声明的变量、const声明的变量和符号常量三者的区别:

  • 符号常量(宏常量)是由 #define 定义的,没有类型,在预编译过程中已经被替换,不会给其分配内存,常用于固定数值(一改全改)
  • const声明的变量 有类型,占存储单元,const定义后就不能修改,因此定义时要初始化,只是在程序运行过程中不改变其值,它是全局的不变量
  • static声明的变量有两种情况,同上


static声明的函数、const声明的函数的区别:

  • static void total_disp():函数前 加static使得函数成为静态函数。此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件,所以又称内部函数。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名
  • 当 const 在函数名前面的时候修饰的是函数返回值;在函数名后面表示是 C++ 常成员函数,该函数不能修改对象内的任何成员,只能发生读操作,不能发生写操作。Const具体


6.struct和class的区别是什么?详述在两种语言中不同

struct是C语言中的结构体类型,而class是C++中的类

  • C语言中的struct只能定义成员变量,不能够定义成员函数
  • 在c++中的struct有构造函数和成员函数,并且有class的其他特性
  • c++中的struct默认的成员是public的,而class中成员默认是private的


7.break和continue的区别?

  • continue语句只结束本次循环,继续执行下一次循环,而不是终止整个循环。continue只能在循环语句中使用(特别是不能在switch中使用,除非switch在循环体中,此时continue表示的也是结束循环体的本次循环,与switch的执行没有关联)
  • break语句则是结束整个循环过程,不再判断执行循环的条件是否成立,break可以在switch中使用


8.typedef和#define的区别?

  1. typedef是一种在计算机编程语言中用来声明自定义数据类型,配合各种原有数据类型来达到简化编程的目的的类型定义关键字;而#define是预处理指令
  2. 关键字typedef在编译阶段有效,由于是在编译阶段,因此typedef有类型检查的功能;#define则是宏定义,发生在预处理阶段,值进行简单的字符串替换,而不进行任何检查
  3. typedef是用于为已有数据类型取‘别名’,给定义数组、指针、结构等类型带来了极大的方便,不仅是程序书写简单且是意义更为明确,可以增加可读性


9.指针和数组的对比?

  1. 指针保存数据的地址,任何存入指针变量p的数据都会被当作地址来处理;数据保存数据,数组a代表的是数组首元素的首地址而不是数组的首地址
  2. 指针间接访问数据,首先取得指针变量p的内容,用这个内容当作地址去寻找数据或向这个地址写入数据;数组直接访问数据,数组a既是整个数组的名字
  3. 指针通常用于动态数据结构(不确定总的数据存储容量,先为现有的数据元素定义一个确定的初始大小的空间,若干个数据元素分配若干个相同大小的空间。当问题规模发生变化时,动态的向系统增加或减少空间);数组通常用于存储固定数值且数据类型相同的元素


10.谈一下指针数组和数组指针的内存布局?

指针数组和数组指针的区别?

  1. 指针数组其实是一个数组,元素都是指针,数组占多少字节有数组本身决定,即存储指针的数组
  2. 数组指针是一个指针,它指向一个数组,代表了数组的地址

指针函数和函数指针的区别?详述

  • 指针函数就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针,即一个地址
  • 函数指针是一个指向函数的指针,函数指针是需要把一个函数的地址赋值给它


具体问答:

1.C语言中的运算符优先级关系?

 !和~的区别与作用

<<与<<=的区别:就跟+和+=一样

#include<iostream>

int main(){
	int a=1;
	printf("%d\n",a<<1);
	printf("%d\n",a);
	a<<=1;
	printf("%d",a);
	return 0;
}

2
1
2


2.谈一下什么是函数,有哪些特点?

函数是用户与程序的接口。

  • 一个函数定义包含函数头和函数体两部分。函数名、参数表和返回类型这三部分一般称为函数头,用{}括起来的部分为函数体
  • 函数名:要符合标识符的命名规则
  • 参数表:函数定义时的参数又称为形式参数(形参),可以含有零个或多个参数
  • 函数调用过程:实参给形参赋初值,接着函数体对形参做相应处理,最后把处理结果作为函数值返回个调用者
  • 函数的声明:
    1. 如果函数调用前,没有对函数作声明,且同一源文件的前面出现了该函数的定义,那么编译器就会记住它的参数数量和类型以及函数的返回值类型,即把它作为函数的声明,并将函数返回值的类型默认为int型。

    2. 如果在同一源文件的前面没有该函数的定义,则需要提供该函数的函数原型。用户自定义的函数原型通常可以一起写在头文件中,通过头文件引用的方式来声明。


3.谈一下C语言程序运行的编译和链接过程

实现一个C语言程序,需要四个步骤:预编译、编译、链接、运行。每个步骤完成不同的功能,并生成不同类型的文件,其中某一步出错必须重新预编译后再调试。

  • 编译过程:编译是将编辑好的C语言源程序翻译成二进制目标代码的过程,使用C语言提供的编译程序(编译器)完成的。编译时,要对每一天语句进行语法检查,直到排除所有的语法错误后才在磁盘上生成目标文件
  • 链接过程:是将目标文件和其他分别进行编译生成的目标程序模块以及系统提供的标准库函数“合成”在一起,生成可以运行的可执行文件(.exe文件)的过程。链接过程使用C语言提供的链接程序完成,生成的可执行文件存放在磁盘中。


4.谈一下什么是指针?指针有哪些特点?

  • 指针变量的本质是用来放地址;一般的变量是用来放数值的
  • 内存中 每一个存储单元都有其存储地址,根据存储地址可以准确找出该内存单元,一个指针变量的值就是每一个存储单元的地址
  • 指针变量是一个变量,可以被赋予不同的指针值


5.⭐谈一下内存分配和内存释放? 又分为内存的动态静态分配 和 程序的动态静态分配

(以下是内存的动态静态分配,分配方式不同,最典型的是数组的静态实现和动态实现)

  •  内存分配:分为静态内存分配和动态内存分配
    • 静态内存分配:是指定义了数组或变量以后,系统会根据他们的数据类型和大小为其分配相应的内存单元。这些内存在程序运行钱就分配好了,不可改变。
    • 动态内存分配是程序在实际运行过程中,根据程序的实际需要来分配内存单元
  • 内存释放:在函数内部定义的静态分配的局部变量,当函数运行结束后,系统自动将内存释放,如数组;在函数内部定义的动态分配的变量,系统不会自动释放,会通过函数free来释放相应的内存空间,如malloc申请的空间

(程序的动态静态分配可以根据变量的存储类别来分配,见书P205,释放即根据存储类别的生命周期即可)

  • 内存分配:
    • 存储类别指的是数据在内存中存储的方式:自动的(auto)、静态的(static)、寄存器的(register)、外部的(extern)
    • 一个正在运行着的C编译程序占用的内存分为 代码区、初始化数据区(数据段)、未初始化数据区(BSS段)、堆区 和 栈区 5个部分。
    • 动态分配内存:如果局部变量不专门声明为static,则一般都是auto变量,即动态分配存储空间,数据存储在动态存储区(逻辑上分为程序区、静态存储区,动态存储区)。一般为形参和函数中定义的局部变量
    • 静态分配内存:所谓静态存储方式是指在程序编译期间分配固定的存储空间的方式。该存储方式通常是在变量定义时就分定存储单元并一直保持不变, 直至整个程序结束。全局变量、静态局部变量、静态外部变量、外部变量 就属于此类存储方式,数据存储在静态存储区中 
  • 内存释放:根据存储类别的生命周期判断


6.谈一下什么是多级指针?

  • *p是一级指针:存储变量的地址
  • **p是二级指针:存储一级指针的地址


7.什么是形式参数?

程序编译时,并不为形参分配存储空间,只有在调用到此函数的时候,形式参数才临时占用存储空间,而执行函数完毕后就会被释放


8.谈一下strlen和sizeof的区别?

  • sizeof相当于一个宏一样的东西,它是一个运算符,而不是函数,编译的时候展开为常数,编译的时候有每个变量的定义表,sizeof通过查表确定变量的占用空间。返回的是占用的字节数
#include<iostream>
#include<stddef.h>

int main(){
	
	char a[] = "abcd";
	char b[] = {'a','b','c','d'};
	int c[3];
	
	printf("%d\n",sizeof(a));
	printf("%d\n",sizeof(b));
	printf("%d\n",sizeof(c));
	return 0;
}

5 (包含了一个终止符)

12

  • strlen是计算字符串长度的库函数名,它是一个函数,参数是const char* ,在头文件<string.h>中,遇到 ‘\0’ 结束,不包括 '\0' 在内


9.谈一下 #include<file.h> 和 #include "file.h" 区别?

  • 使用<>方式通常指首先在编译器默认的include目录下寻找该头文件,一般使用编译器提供的标准函数库就用这种方式进行引入文件
  • 使用”“方式通常指首先在源码当前目录下面寻找该头文件,一般自己定义的头文件就用这个
  • 两者区别:搜索头文件的目录不同


10.谈一下局部变量能否和全局变量重名?

  1. 能,但是局部变量会屏蔽全局变量
  2. 要用全局变量,需要使用 ”::“
  3. 在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量
  4. 对有些编译器而言,在同一个函数内可以定义多个同名的局部变量,如在不同的循环中定义同名的变量


11.⭐程序的内存分配

  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量的值等。
  • 堆区:一般由程序员释放,若诚序言不释放,程序结束时可能由OS回收,分配方式类似于链表,同数据结构中的堆无关
  • BBS段:通常是用来存放未初始化的全局变量和未初始化的静态变量的一块区域。程序结束后由系统释放,属于静态内存分配
  • 数据段:通常是指用来存放程序中已初始化的全局变量和已初始化的静态局部变量的一块内存区域。属于静态内存分配。
  • 代码段:通常是指用来存放程序执行代码的一块内存区域,在运行前内存区域大小已经确定,且该区域通常属于 只读,在代码段中,也有可能包含一些只读的常数变量,如字符串常量等。程序段时程序代码在内存中的映射(一个程序可以在内存中有多个副本)

C中只规定了变量的存储期,不规定存储在哪个段


12.谈一下什么时内联函数(inline)?

  1. 定义:指那些定义在类体内的成员函数,即该函数的函数体放在类体内
  2. 特点:其他通用函数有的它都有,只有在调用上不同。调用一般函数时,程序的执行权转移到被调用函数,然后返回到调用它的函数中继续执行;调用内联函数时,调用语句是由内联函数的主体去替换的(当编译器发现某段代码在调用一个内联函数时,他不是去调用该函数,而是将该函数的代码整段插入到当前位置)
  3. 优点:省去了调用的过程(在C语言中,如果一些函数被频繁调用,不断地有函数入栈,即函数栈,会造成栈空间或栈空间的大量消耗。为了解决这个问题,特别的引入了inline修饰符,表示为内联函数,加快了程序运行速度
  4. 缺点:每当调用到内联函数时都需要插入代码,会增大程序的体积
  5. 用法:
    #include <stdio.h>  
     
    //函数定义为inline即:内联函数  
    inline char* dbtest(int a) {  
    	return (i % 2 > 0) ? "奇" : "偶";  
    }   
      
    int main(){  
    	int i = 0;  
    	for (i=1; i < 100; i++) {  
    		printf("i:%d    奇偶性:%s /n", i, dbtest(i));      
    	}  
    } 
    void Foo(int x, int y);
    inline void Foo(int x, int y) // inline 与函数定义体放在一起
    {
    }

    关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。所以说 inline 是一种 “用于实现的关键字” ,而不是一种“用于声明的关键字”

  6. 使用内联函数的限制: inline只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句例如while、switch,并且内联函数本身不能是直接递归函数(自己内部还调用自己的函数)。


13.描述一下gcc的编译过程?

gcc编译过程分为4个阶段:预处理、编译、汇编、链接

  1. 预处理:头文件包含、宏替换、条件编译、删除注释
  2. 编译:主要进行词法、语法、语义分析,检查无误后将与处理好的文件编译成汇编文件
  3. 汇编:将汇编文件 转换成 二进制文件
  4. 链接:将项目中的各个二进制文件+库函数+启动代码 链接成 可执行文件


14.变量的命名规则

变量名由数字、字母、下划线组成,不能以数字开头


15.函数的定义与函数的声明的区别

  • 函数定义:是对函数功能的确立,包含函数名、函数类型、形参及其类型、函数体等,是一个完整的、独立的函数单位
  • 函数声明:是把函数的名字、函数的类型以及函数的形参(顺序和个数要相同)通知编译系统,以便在对包含函数调用的语句进行编译时,据此对其进行对照检查


16.⭐结构体和共用体的区别?

结构体中的成员拥有独立的空间(累和,所有数据类型的长度总和);

共用体的成员共享用一块空间,但是每个共用体成员能访问共用区的空间大小是由成员自身的类型决定的(由共用体内的最长的数据类型所决定的大小)


17.谈一下哪些情况下会出现野指针,如何避免?

野指针是指 未初始化的指针,这些指针指向一个随意的内存地址,如果访问他们可能会导致很坏的程序行为,甚至导致程序崩溃

  1. 指针变量声明时没有被初始化。 解决方法 :指针声明时初始化,可以是具体的地址值,也可以让其指向NULL
  2. 指针p被free或者delete后,没有被置为NULL。解决方法 :指针指向的内存空间被释放后指针应该指向NULL
  3. 指针操作超越了变量的作用范围。 解决方法 :在变量的作用域结束前释放掉变量的地址空间并且让指针指向NULL


18.⭐谈一下如何理解结构体的浅拷贝与深拷贝?

当结构体中有指针成员时容易出现浅拷贝和深拷贝的问题

  • 浅拷贝:两个结构体变量的指针成员指向同一块堆区空间,在各个结构体变量释放的时候会出现多次释放同一段堆区空间的情况
  • 深拷贝:让两个结构体变量的指针成员分别指向不同的堆区空间,只是空间内容拷贝一份,这样在各个结构体变量释放的时候就不会出现多次释放同一段堆区空间的问题


19.谈一下如何理解库函数?

  • 库是已经写好的、成熟的、可复用的代码。每个程序都需要依赖很多底层库,不可能每个人的代码都从零开始,因此库的存在十分重要。
  • 一些公共代码需要反复使用的就把这些编译为库文件
  • 库可以简单看成一组目标文件的集合,将这些目标文件经过压缩打包之后形成的一个文件

在windows上,最常用的C语言库是由集成开发环境所附带的运行库,一般由编译厂商提供


20.定义常量的方法和区别?见问题5


21.C语言的数据类型有哪些?

  1. 基本类型:整型、字符型、浮点型 
  2. 派生类型:指针类型、数组类型、结构体类型、共用体类型、函数类型
  3. 枚举类型:enum 枚举名 {元素1,元素2,......};枚举类型的每一个元素都有其代表的值。未指定默认第一个元素为0,后面一次累加。


22.谈一下 i++ 和 ++i 的区别,哪个速度更快?

++i 更快:因为前置操作需要做的工作更少,只需要加1后返回加1后的结果即可。而后置操作符则必须先保存操作数原来的值,以便返回未加1之前的值作为操作的结果。


23.谈一下abs和fabs的区别?

  1. 都是取绝对值的
  2. 前者abs针对整型,在stdlib.h中
  3. 后者fabs针对浮点型,在math.h中


24.谈一下什么是指针的指针?

即指一个指向指针所在地址的指针,存放的是一级指针的地址,就是二级指针**p


25.⭐解释一下模块化编程?

将系统的各个功能进行封装,变成一个个独立的模块,其他人只需要使用你所提供的函数和变量等,就可以完成相对应的功能


26.strcpy 、sprintf 和 memcpy 的区别

  1. 操作对象不同:strcpy 的两个操作对象均为字符串; sprintf 的 源操作对象 可以是多种数据类型,目的操作对象 是字符串;memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型
  2. 执行效率不同:memcpy 最高,strcpy 次之,sprintf 效率最低
  3. 实现功能不同:strcpy 主要实现字符串变量间的拷贝 ;sprintf 主要实现其他数据类型格式到字符串的转化;memcpy 主要是内存块间的拷贝


27. && 和 & 、|| 和 | 的区别

  1. & 和 | 对操作数进行求值运算 ,&& 和 || 只是判断逻辑关系
  2. && 和 || 在判断左侧操作数就能确定结果的情况下就不再对右侧操作数判断了


28.static全局变量和普通的全局变量有什么区别?

  • 相同:全局变量(外部变量)用static声明就构成了静态的全局变量,全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式,在存储方式上并无不同
  • 区别:在作用域上。如果普通的全局变量可以被源程序的多个文件访问,但是静态全局变量被限制了作用域,只能本文件访问。(由于静态全局变量只能被一个源文件的函数公用,则可以避免在其他源文件中引起错误)


29.do...while 和 while 的区别?

  1. do...while 先执行一次在判断 ,while 是先判断 再执行
  2. do...while 至少执行一次,while 至少执行零次


30.C语言中的语法

例如,结构体如何赋值,有几种形式?


  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值