c语言重难点汇总

一.什么是预编译/预处理,何时需要预编译?

答:预编译又称为预处理,是做些代码文本的替换工作。处理#开头的指令,比如拷贝#include包含的文件代码,#define宏定义的替换,条件编译等,就是为编译做的预备工作的阶段,主要处理#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。

c编译系统在对程序进行通常的编译之前,先进行预处理。c提供的预处理功能主要有以下三种:1)宏定义,注意在定义宏的时候如果有表达式为了避免出错要加括号 2)文件包含 3)条件编译

  1.  总是使用不经常改动的大型代码体。

2.程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。

二.编译宏的时候#和##的作用

在C语言中,#和##是预处理指令中的两个特殊操作符,用于对宏进行操作。

1. #操作符(字符串化操作符):#操作符将宏参数转换为字符串常量。在宏定义中,#操作符可以将宏参数转换为字符串表示。例如:

#define STRINGIFY(x) #x

2. ##操作符(连接操作符):##操作符用于将宏参数连接在一起。在宏定义中,##操作符可以将两个宏参数连接成一个单独的标识符。例如:

#define CONCAT(x, y) x##y

CONCAT(12,20)结果是1220

这两个操作符结合起来可以用于创建更加灵活和复杂的宏定义,提高代码的可重用性和灵活性。

三..h头文件中的ifndef/define/endif 的作用?

答:防止该头文件被重复引用。

在C语言中,可以使用预处理指令 `#ifndef`、`#define`、`#endif` 来避免头文件被重复引用。这个技术被称为**头文件保护**或**头文件防止重复包含**。

具体步骤如下:(注意是在头文件里面写)

1. 在头文件的开头加上 `#ifndef` 指令,后面紧跟一个宏名称,通常是头文件名大写加下划线,例如 `#ifndef EXAMPLE_H_`。

2. 紧接着在下一行加上 `#define` 指令,定义和上面一样的宏名称,例如 `#define EXAMPLE_H_`。

3. 接着是头文件的内容。

4. 最后,在文件的末尾加上 `#endif` 来结束这个条件判断块。

这样,在第一次引用头文件时,`#ifndef` 判断宏是否已经定义过,如果没有定义过,则会定义宏并包含头文件内容,否则直接跳过头文件内容,避免重复引用。

示例代码如下:

#ifndef EXAMPLE_H_

#define EXAMPLE_H_

// 头文件内容

#endif

通过这种方法,可以有效避免头文件被重复引用,确保程序的正常编译和运行。

四.说一下 static  关键字的作用

Static的用途主要有两个,一是用于修饰存储类型使之成为静态存储类型,二是用于修饰链接属性使之成为内部链接属性。

1静态存储类型:

函数内定义的静态局部变量,该变量存在内存的静态区,所以即使该函数运行结束,静态变量的值不会被销毁,函数下次运行时能仍用到这个值

函数外定义的静态变量——静态全局变量,该变量的作用域只能在定义该变量的文件中,不能被其他文件通过extern引用

内部链接属性

       静态函数只能在声明它的源文件中使用

五.const关键字的作用?

答:

1声明常变量,使得指定的变量不能被修改。

const int a = 5;/*a的值一直为5,不能被改变*/

const int b; b = 10;/*b的值被赋值为10后,不能被改变*/

const int *ptr; /*ptr为指向整型常量的指针,ptr的值可以修改,也就是地址可以修改,但不能修改其所指向的值

int *const ptr;/*ptr为指向整型的常量指针ptr的值不能修改,也就是地址不能改变,但可以修改其所指向的值

const int *const ptr;/*ptr为指向整型常量的常量指针ptr及其指向的值都不能修改*/

2修饰函数形参,使得形参在函数内不能被修改,表示输入参数。

int fun(const int a);int fun(const char *str);

3修饰函数返回值,使得函数的返回值不能被修改。

const char *getstr(void);使用:const *str= getstr();

const int getint(void);  使用:const int a =getint();

六.extern关键字的作用?

答:

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

extern int a;

extern int *p;

extern int array[];

extern void fun(void);

用于extern “c

extern “c”的作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的编译方式进行编译,而不是C++的。

PS:本人没有学习C++,所以对此知识不了解,留待之后完善。

七.sizeof 和 strlen 的区别

sizeof 是一个操作符,strlen 是库函数string中的函数。#include “string.h”

sizeof计算字符串长度包括字符串结尾’\0’,strlen不包括’\0’。并且 sizeof计算的是数据类型占内存的大小,而 strlen 计算的是字符串实际的长度(不含’\0’)。

注:不要用sizeof求存储在数组的字符串长度,那样求得的值是数组的长度。;不能对结构体中的位域成员使用sizeof

Eg: int b[10]; sizeof(b)为数组的大小,4*10

sizeof 的参数可以是数据的类型,也可以是变量,而 strlen 只能以结尾为‘\0‘的字符串作参数,以‘\0’判断字符串是否到结尾。

编译器在编译时就计算出了 sizeof 的结果。而 strlen 函数必须在运行时才能计算出来。

八.你知道哪些对结构体赋值的方法?

1.初始化struct tag          

{

     chara;

   int b;

}x = {‘A’, 1}

struct tag

{

char a;

int b;

};

struct tag x = {‘A’,1};/*在定义变量时初始化

  1. 先定义变量,然后进行赋值

struct tag

{

char a;

int b;

};

struct tag x;/*定义变量*/

x.a = ‘A’;/*按字段赋值*/

x.b = 1; /*按字段赋值*/

而当你使用初始化的方式来赋值时,如x = {‘A’,1};则出错

3.结构变量之间可以直接赋值

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

int memcmp(const void *s1, const void *s2, size_t n)

int  main( int  argc, char**  argv )参数意义

第一个argc,是记录你输入在命令行上的参数(字符串)个数;

第二个argv[]是个指向字符串的指针数组,即数组元素是指向输入在命令行上的每个参数(字符串)的指针。

argv[0]:存储程序名;

argv[1]:指向在命令行中执行程序名后的第一个字符串;

argv[2]:指向第二个字符串。

十一.结构体位域

位域是一个或多个位的字段,不同长度的字段(如声明为unsigned int类型)存储于一个或多个其所声明类型的变量中(如整型变量中)。

位域的类型:可以是charshortint,多数使用int,使用时最好带上signedunsigned

位域的特点:字段可以不命名,如unsignedint :1;可用来填充;unsigned int :0; 0宽度用来强制在下一个整型(因此处是unsigned int类型)边界上对齐。

位域的定义

struct st1

{

unsigned char a:7;/*字段a占用了一个字节的7bit*/

unsigned char b:2;/*字段b占用了2bit*/

unsigned char c:7;/*字段c占用了7bit*/

}s1;

sizeof(s1)等于3。因为一个位域字段必须存储在其位域类型的一个单元所占空间中,不能横跨两个该位域类型的单元。也就是说,当某个位域字段正处于两个该位域类型的单元中间时,只使用第二个单元,第一个单元剩余的bit位置补(pad0

于是可知Sizeof(s2)等于3*sizeof(int)12

struct st2

{

unsigned int a:31;

unsigned int b:2;/*前一个整型变量只剩下1bit,容不下2bit,所以只能存放在下一个整型变量*/

unsigned int c:31;

}s2;

位域的好处

       1.有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0两种状态,用一位二进位即可。这样节省存储空间,而且处理简便。这样就可以把几个不同的对象用一个字节的二进制位域来表示。允许你在结构体中定义成员变量占用特定位数的bit,这样可以有效地节省内存空间
        2.可以很方便的利用位域把一个变量给按位分解。比如只需要4个大小在03的随即数,就可以只rand()一次,然后每个位域取2个二进制位即可,省时省空间。

位域的缺点:

不同系统对位域的处理可能有不同的结果,如位段成员在内存中是从左向右分配的还是从右向左分配的,所以位域的使用不利于程序的可移植性。

十二.函数参数入栈顺序

答:

C语言函数参数入栈顺序是从右向左的,这是由编译器决定的,更具体的说是函数调用约定决定了参数的入栈顺序

十三.malloc(0)返回值malloc和free都是在stdlib.h库里面的函数

答:如果请求的长度为0,则标准C语言函数malloc返回一个null指针或不能用于访问对象的非null指针,该指针能被free安全使用。

十四.do……while和while……do有什么区别?

答 、前一个循环一遍再判断,后一个判断以后再循环。

十五.程序的内存分配
 

答:一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap)—一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事
3、全局区(静态区)(static)—全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
4、文字常量区—常量字符串就是放在这里的。程序结束后由系统释放。
5、程序代码区—存放函数体的二进制代码

int a=0;    //全局初始化区
  char *p1;   //全局未初始化区
  main()
  {
   intb;栈
   char s[]="abc";   //栈
   char *p2;         //栈
   char *p3="123456";   //123456\0在常量区,p3在栈上。
   static int c=0;   //全局(静态)初始化区
   p1 = (char*)malloc(10);
   p2 = (char*)malloc(20);   //分配得来得10和20字节的区域就在堆区。
   strcpy(p1,"123456");   //123456\0放在常量区,编译器可能会将它与p3所向"123456"优化成一个地方。
}

十六.请回答一下数组和指针的区别

指针 int * ptr=&a

ptr的内容是a的地址,指向的值是整数a的值

指针是指向另一个内存的变量,其内容是所指内存的地址,指针有一块自己的内存

保存数据的地址,需要*间接访问数据     

2.数组

保存数据,通过索引直接访问数据,通常用于固定数目且数据类型相同的元素。数组名是指针常量,是指向数组第一个元素的地址。

指针常量 即指针的所指向的地址不能发生改变,但指针指向的内容可以改变;常量指针 即指针所指向的内容不能改变,但指针的地址可以改变

注:指针指向一个数组时,可用下标访问和解引用访问,此时指针和数组名作用差不多。

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
    (1)修改内容上的差别
      char a[] = “hello”;
      a[0] = ‘X’;
      char *p = “world”; // 注意p 指向常量字符串
      p[0] = ‘X’; // 编译器不能发现该错误,运行时错误
   (2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个 指针变量的字节数,而不是p 所指的内存容量。

十七.请回答下指针数组和数组指针的区别

指针数组,是指一个数组里面装着指针,也即指针数组是一个数组。

如int *a[10],一个有10个指针的数组,每个指针指向一个整形数。

2. 数组指针,是指一个指向数组的指针,它其实还是指针,只不过它指向整个数组。

如int (*p)[10],一个指向有10个元素整型数组的指针。注:[]优先级高于*。

应用:

  1. 多个字符串一般可存储在二维数组或指针数组中,所以指针数组用于存放多个字符串,或作为函数参数传递多个字符串;
  2. 注:a、二级指针和指针数组某些时候是一个意思。

 b、指针数组(也就是元素为指针类型的数组)常常作为二维数组的一种便捷替代方式。

一般情况下,这种数组中的指针会指向动态分配的内存区域。

  1. 数组指针一般用于指向一个二维数组(其类型是一个指向一维数组的指针)来访问数组的元素。
  1. 遍历数组:通过数组指针可以方便地遍历数组中的元素,而不需要使用数组下标。
  2. 作为函数参数:可以将数组指针作为函数参数传递,从而在函数内部对数组进行操作。
  3. 动态内存分配:在动态分配内存时,可以使用数组指针来访问和操作分配的内存块。
  4. 多维数组:对于多维数组,可以使用数组指针简化对数组的操作。

int main() { int arr[] = {1, 2, 3, 4, 5};

int *ptr = arr; // 数组指针指向数组的第一个元素

// 遍历数组元素

    for (int i = 0; i < 5; i++) {

        printf("Element %d: %d\n", i, *ptr);

        ptr++; // 指针移动到下一个元素

    }

    

    return 0;

}

十八.请回答下指针函数和函数指针的区别

指针函数,其本质是一个函数,不过它的返回值是一个指针,如:int *fun(int,int)

函数指针,其本质是一个指针,该指针指向了一个函数,所以它是指向函数的指针。

     如:int (*p)(int, int)  p=func, 初始化时给函数名,函数名是该函数的入口地址。

回调函数,就是一个将函数指针作为参数来实现调用其它函数的函数。

     如:void function( int a, int (*p)(int , int) )

十九.请你回答一下野指针是什么?

野指针指向了一块随机内存空间,不受程序控制。如1、未初始化的指针,指针指向一个已删除的对象或者指向一块没有访问权限的内存空间;2、free(p)后,p没有置null,。注:指针释放需置NULL。

二十.堆和栈的区别?

1)申请方式:

栈由系统自动分配和管理,堆由程序员手动分配和管理。

2)效率:

栈由系统分配,速度快,不会有内存碎片。

堆由程序员分配,速度较慢,可能由于操作不当产生内存碎片。(看成一个链表类似的结构)

3)扩展方向

栈从高地址向低地址进行扩展,堆由低地址向高地址进行扩展。

  1. 程序局部变量是使用的栈空间,new/malloc 动态申请的内存是堆空间,函数调用时会进行形参和返回值的压栈出栈,也是用的栈空间

二十一.链表和数组有什么区别

数组和链表有以下几点不同:

1)存储形式:数组是一块连续的空间,声明时就要确定长度。链表是一块可不连续的动态空间,长度可变,每个结点要保存相邻结点指针。

2)数据查找:数组的线性查找速度快,查找操作直接使用偏移地址。链表需要按顺序检索结点,效率低。

3)数据插入或删除:链表可以快速插入和删除结点,而数组则可能需要大量数据移动。

4)越界问题:链表不存在越界问题,数组有越界问题。

说明:在选择数组或链表数据结构时,一定要根据实际需要进行选择。数组便于查询,链表便于插入删除。数组节省空间但是长度固定,链表虽然变长但是占了更多的存储空间。

二十二.论述含参数的宏与函数的优缺点

答:                       带参宏                               函数

           处理时间   编译时                                  程序运行时

           参数类型   没有参数类型问题                定义实参、形参类型

           处理过程  不分配内存                            分配内存

           程序长度   变长                                      不变

           运行速度   不占运行时间调用和返回      占用时间

二十三.头文件的作用是什么?

答:1、通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要

向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,

而不必关心接口怎么实现的。编译器会从库中提取相应的代码。

2、头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。

二十四.谈谈你对编程规范的理解或认识

编程规范可总结为:程序的可行性,可读性、可移植性以及可测试性

  • 22
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值