推广 热搜: csgo  vue  angelababy  2023  gps  新车  htc  落地  app  p2p 

:C语言笔记第一时间查看预编译何时需要预处理

   2023-06-05 网络整理佚名2030
核心提示:这样问题就来了,如果在C++中调用的函数如上例中的fun(1,2)是用C语言在源文件.的结果等于对象或类型所占的内存字节数。与free是C/C++语言的标准库函数,new/是C++的运算符。c里面的没有成员函数,不能继承,派生等等.解答:因为str1没有结束符\0,故而复制的字符数不确定。解答:C语言中的函数参数为传值参数,在函数内对形参的修改并不能改变对应实参的值。

点击上方“C语言中文社区”,选择“置顶公众号”第一时间查看C语言笔记!

原文链接:https://zhuanlan.zhihu.com/p/100070340

目录

1 预处理

问题一:什么是预编译? 什么时候需要预编译?

问题 2:编写一个“标准”宏,它接受两个参数并返回较小的一个

问题三:#和##的作用是什么?

问题四:如何避免重复包含头文件?

2个关键词

问题一:关键词的作用是什么?

问题2:const关键字的作用是什么?

问题三:关键词的作用是什么?

问题四:关键词的作用是什么?

2代表“C”

问题五:关键词的作用是什么?

3个结构

问题一:结构赋值?

问题2:如何比较结构变量

问题 3:结构体字段

问题4:结构体成员数组的大小为0

4个功能

问题一:函数参数入栈的顺序

问题 2:内联函数

5 内存分配回收

问题一:/free和new/的区别

问题2:(0)返回值

6 变量参数表

问题一:实现功能

7 其他

问题一:()的作用

问题2:(“暂停”)的作用;

问题三:C++类和C类有什么区别?

8 找错题

9道编程题

问题一:判断字符串str2是否在字符串str1中。

1 预处理

问题一:什么是预编译? 什么时候需要预编译?

回答:

预编译,又称预处理,是整个编译过程中首先完成的工作,即程序执行前的一些预处理工作。 它主要处理以#开头的命令。 比如复制#中包含的文件代码,替换#定义的宏,条件编译#if等。

当需要预编译时:

1. 始终使用不经常更改的大型代码体。

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

问题 2:编写一个“标准”宏,它接受两个参数并返回较小的一个

回答:

#define MIN(x, y) ((x)<(y)?(x):(y)) //结尾没有;

问题三:#和##的作用是什么?

答:#是将宏参数转换为字符串的运算符,##是连接两个宏参数的运算符。

例如:

#define STR(arg) #arg //则宏STR(hello)展开时为”hello”#define NAME(y) name_y //则宏NAME(1)展开时仍为name_y#define NAME(y) name_##y //则宏NAME(1)展开为name_1#define DECLARE(name, type) typename##_##type##_type //则宏DECLARE(val, int)展开为int val_int_type

问题四:如何避免重复包含头文件?

回答:

例如,为了避免重复包含头文件.h,可以在其中使用条件编译:

#ifndef _MY_HEAD_H#define _MY_HEAD_H #endif

2 关键词问题1:关键词有什么作用?

回答:

主要有两个用途,一个是修改存储类型,使其成为静态存储类型,另一个是修改链接属性,使其成为内部链接属性。

1 静态存储类型:

函数中定义的静态局部变量,该变量存在于内存的静态区域,所以即使函数结束,静态变量的值也不会被销毁,函数执行时仍然可以使用该值下次运行。

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

2 内部链接属性

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

问题2:const关键字的作用是什么?

回答:

1 声明一个常量变量,使指定的变量不能被修改,例如:

const int a = 5;const int b; b = 10;const int *ptr; int *const ptr;const int *const 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();

问题三:关键词的作用是什么?

回答:

指定的关键字可能会被系统、硬件、进程和线程更改,迫使编译器每次都从内存中获取变量的值,而不是从优化的寄存器中读取。 示例:硬件时钟; 多线程中多个任务共享的变量等。

问题四:关键词的作用是什么?

回答:

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

extern int a;extern int *p;extern int array[];extern void fun(void);

其中,函数声明中的关键字仅暗示该函数可能在其他源文件中定义,没有其他作用。 喜欢:

头文件 A:.h 包含:

extern int func(int a, int b);

在源文件 A:.c 中:

#include “A_MODULE.h”
int func(int a, int b){    return a+b;}

此时,将头文件.h展开后为:

extern int func(int a, int b);int func(int a, int b){    return a+b;}

和源文件 B:.c:

#include “A_MODULE.h”int ret = func(10,5);/

将头文件.h展开后为:

extern int func(int a, int b);int ret = func(10,5);

2为“c

“c”的作用是正确实现C++代码,以调用其他C语言代码。 加上“C”之后,会指示编译器按照C语言的编译方式来编译这部分代码,而不是C++。

作为与C兼容的语言,C++保留了一些过程语言的特点,比如可以定义不属于任何类的全局变量和函数,但C++毕竟是面向对象的语言。 为了支持函数重载,的编译方式与C不同。例如,在C++中,函数的编译名称可能是void fun(int, int),但C中没有重载机制,而函数的名称 一般直接用来指定编译函数的名字,比如上面的 name可能是_fun。

出现这个问题,如果在C++中调用的函数,如上例中的fun(1,2),是在C语言的源文件.c中实现编译的,那么目标文件中函数fun的函数名.obj是_fun,C++通过调用其外部提供的头文件.h引用源文件.cpp后调用fun,然后直接按照C++编译的方式进行编译,使得fun后的目标文件.obj的名字编译出来的是, 这样在链接的时候,因为目标文件.obj中不存在该函数,导致链接错误。

解决方法是让.cpp知道函数fun是用C语言实现和编译的,调用的时候按照C语言的方式编译。 这个方法可以用“C”来实现(具体用法见下文)。 一般在用C语言实现一个函数时,要考虑到这个函数可能被C++程序调用,所以在设计头文件时,头文件应该声明如下:

#ifdef __cplusplusextern “C”{#endif  extern void fun(int a, int b);#ifdef __cplusplus}#endif

如何使用“C”

1.可以是单条语句

extern "C" doublesqrt(double);

2.可以是复合语句,相当于在复合语句中的声明后面加上“C”

extern "C"{  double sqrt(double);  int min(int, int);}

3.可以包含头文件,相当于在头文件中的声明中加上“C”

extern "C"{  #include }

4.函数内不能加“C”

5、如果函数有多个声明,可以在所有声明后都加上“C”,或者只出现在第一个声明中,后续声明都接受第一个链接指标的规则。

6、除“C”外,还有“”等。

问题五:关键词的作用是什么?

回答:

在编译时处理,不能编译成机器码。 结果等于对象或类型占用的内存字节数。 的返回值类型为 。

3 结构问题1:结构的赋值?

回答:

在C语言中,对结构体变量的赋值要么是初始化,要么是定义后按域赋值。

方法一:初始化

struct tag{  chara;  int b;}x = {‘A’, 1};

或者

struct tag{  char a;  int b;};struct tag x = {‘A’,1};

GNU C 中提供了另一种方法:

struct tag{  char a;  int b;}x ={  .a = ‘A’,  .b =1;};

或者

struct tag{  char a;  int b;};struct tag x ={  .a= ‘A’,  .b=1,};

方法二:定义变量后按字段赋值

struct tag{  char a;  int b;};struct tag x;x.a = ‘A’;x.b = 1; 

而当你使用初始化方法赋值时,比如x = {'A',1}; 发生错误。

方法三:结构变量之间的赋值

struct tag{  chara;  int b;};struct tag x,y;x.a=’A’;x.b=1;y = x;

问题2:如何比较结构变量?

答:虽然结构变量可以直接用=赋值,但是区别是通过==之类的比较器来比较的,因为比较器只作用于基本数据类型。 这时候只有int(const void *s1, const void *s2, n); 可用于内存比较。

问题 3:结构体字段

回答:

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

位域的类型:可以是char,short,int,大部分用int,最好带上或者

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

位域定义:

struct st1{  unsigned chara:7;  unsigned charb:2;  unsigned charc:7;}s1;

(s1)等于3。由于一个位域字段必须存储在其位域类型的一个单元所占用的空间中,因此它不能跨越两个该位域类型的单元。 也就是说,当一个位域域位于该位域类型的两个单元中间时,只使用第二个单元,其余第一个单元的位位置用(pad)0填充。

所以可以看出 (s2) 等于 3*(int) 或 12

struct st2{  unsigned inta:31;  unsigned intb:2;  unsigned int c:31;}s2;

位域的优点:

1、有些信息在存储时,不需要占用一个完整的字节,只需要占用几个或一个二进制位即可。 例如,存储开关量时,只有0和1两种状态,可以使用二进制位。 这节省了存储空间并且易于处理。 这样,一个字节的二进制位域就可以表示几个不同的对象。

2. 使用位域来逐位分解一个变量是非常方便的。 比如你只需要4个大小从0到3的随机数,你可以只rand()一次,然后每个位域取2个二进制位,节省时间和空间。

位域的缺点:

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

问题4:结构体成员数组的大小为0

结构体数组成员零大小是GNU C的一个特性,好处是可以在结构体中分配可变长度的大小。like

typedef struct st{  inta;  int b;  char c[0];}st_t;#define SIZE 100st_t *s = (st_t *)malloc(sizeof(st_t) + SIZE);

4 函数问题1:函数参数入栈的顺序

回答:

C语言函数参数的堆叠顺序是从右到左,由编译器决定。 更具体地说,函数调用约定决定了参数的堆叠顺序。 C语言采用函数调用约定,所以对于函数声明,完整的形式为:int func(int a, int b);

问题 2:内联函数

回答:

该关键字只是建议编译器进行内联展开处理,即将函数直接嵌入到调用程序的主体中,省去了call/指令。

5 内存分配与回收问题1:/free和new/的区别

回答:

1)和free是C/C++语言的标准库函数,new/是C++的运算符。 它们可以用来申请动态内存和释放内存。

2)对于非内部数据类型的对象,单独使用maloc/free不能满足动态对象的要求。 对象在创建时必须自动执行构造函数,对象在死亡前必须自动执行析构函数。 由于/free是库函数而不是操作符,不在编译器的控制权限之内,执行构造函数和析构函数的任务不能强加给/free。 因此,C++语言需要一个可以完成动态内存分配和初始化的 new,以及一个可以完成清理和释放内存的。 请注意,new/ 不是库函数。

我们不要试图使用/free来完成动态对象的内存管理,我们应该使用new/。 由于内部数据类型的“对象”没有构造和销毁过程,所以 /free 和 new/ 与它们是等价的。

3)既然new/的功能完全覆盖了/free,那为什么C++不去掉/free呢? 这是因为C++程序经常调用C函数,而C程序只能使用/free来管理动态内存。

如果用free释放“new创建的动态对象”,该对象可能会因为无法执行析构函数而导致程序错误。 如果释放“申请的动态内存”,结果同样会导致程序出错,但程序的可读性很差。 所以new/必须成对使用,/free也是。

问题2:(0)返回值

答:如果请求的长度为0,标准C语言函数返回空指针或非空指针,不能用来访问对象,free可以安全使用。

6 变量参数表

变量参数列表是通过在作为标准库一部分的 .h 头文件中定义的宏来实现的。 该头文件声明了一个类型和三个宏: 、 和 。

typedef char *va_list;#define va_start(ap, A)  (void)((ap) = (char *)&(A) + _Bnd(A, _AUPBND))#define va_arg(ap, T) (*(T )((ap) += _Bnd(T, _AUPBND)) - _Bnd(T, _ADNBND)))#define va_end(ap) (void)0int print(char *format, …)

宏的第一个参数是类型变量,第二个参数是省略号之前的最后一个命名参数。 作用是初始化类型变量,并将其值设置为变量参数的第一个变量。

宏的第一个参数是类型变量,第二个参数是参数列表中下一个参数的类型。 返回变量的值并使该变量指向下一个可变参数。

在访问最后一个变量参数后调用宏。

问题一:实现功能

int my_printf( const char* format, ...){  va_list arg;  int done = 0;
va_start (arg, format); while( *format != '\0') { if( *format == '%') { if( *(format+1) == 'c' ) { char c = (char)va_arg(arg, int); putc(c, stdout); } else if( *(format+1) == 'd' || *(format+1) == 'i') { char store[20]; int i = va_arg(arg, int); char* str = store; itoa(i, store, 10); while( *str != '\0') putc(*str++, stdout); } else if( *(format+1) == 'o') { char store[20]; int i = va_arg(arg, int); char* str = store; itoa(i, store, 8); while( *str != '\0') putc(*str++, stdout);   }   else if( *(format+1) == 'x') { char store[20]; int i = va_arg(arg, int); char* str = store; itoa(i, store, 16); while( *str != '\0') putc(*str++, stdout);   }   else if( *(format+1) == 's' ) { char* str = va_arg(arg, char*); while( *str != '\0') putc(*str++, stdout);     } // Skip this two characters. format += 2; } else { putc(*format++, stdout); } } va_end (arg);
return done;}

7 其他问题1:()的作用

答:()是调试程序时经常用到的宏。 当程序运行时,它计算括号中的表达式。 如果表达式为 FALSE(0),程序将报错并终止执行。 如果表达式不为 0,则继续执行以下语句。 该宏通常判断程序中是否存在明显的非法数据。 如果有就终止程序,避免造成严重的后果,也方便查找错误。 例如程序中变量n不应该为0。 如果为 0,可能会导致错误。 你可以这样写程序:

//......ASSERT( n != 0);k = 10/ n;//.....

仅在调试版本中有效,如果编译为版本则忽略。

()的功能类似。 它是ANSI C标准中规定的函数。 它和C的一个重要区别是它可以在版本中使用。

问题2:(“暂停”)的作用;

答:对于系统的暂停程序,按任意键继续,屏幕会打印“Press any key to ...”,不用();

问题三:C++类和C类有什么区别?

答:c++中的类有成员保护功能,具有继承、多态等oo特性,而c中的类则没有。 C中没有成员函数,不能继承、派生等。

8 找错题1:

void test1(){  char string[10];  char* str1 = "0123456789";  strcpy(string, str1);}

答:字符串str1有11个字节(包括末尾的终止符'\0'),但只有10个字节,所以会导致数组越界。

问题2:

void test2(){  char string[10], str1[10];  int i;
for(i=0; i<10; i++) { str1= 'a'; } strcpy(string, str1);}

答:因为str1没有结束符'\0',复制的字符数是不确定的。 源代码如下:

#include char *strcpy(char *s1, cosnt char *s2){  char *s = s1;  for (s = s1; (*s++ = *s2++) != '\0';)    ;  return s1;}

问题三:

void test3(char* str1){  char string[10];  if(strlen(str1) <= 10 )  {    strcpy(string, str1);  }}

答:应该改成if ((str1) < 10),因为结果不计入最后的终止符'\0'。 源代码如下:

#include size_t strlen(const char *s){  const char *sc;  for (sc = s; *sc != '\0'; ++sc)    ;  return (sc - s);}

问题四:

void GetMemory(char *p){  p = (char *)malloc( 100 );}void Test( void ){  char *str = NULL;  GetMemory(str);  strcpy(str,"hello world");  printf(str);}

答:C语言中的函数参数是传值参数,函数中形参的修改不能改变对应实参的值。 所以,调用之后,str还是NULL。

问题 5:

char *GetMemory( void ){  char p[] = "hello world";  return p;}void Test( void ){  char *str = NULL;  str = GetMemory();  printf(str);}

答:其中p为局部变量,函数返回后回收。 因此 str 仍然是 NULL

问题六:

void GetMemory( char **p, int num ){  *p = (char *)malloc(num);}void Test( void ){  char *str = NULL;  GetMemory(&str, 100);  strcpy(str, "hello");  printf(str);}

答:问题6回避了问题4的问题,但是没有判断*p为NULL的情况。 当*p不为NULL时,之后空间不空闲

问题七:

void Test( void ){  char *str = (char *)malloc( 100 );  strcpy(str, "hello" );  free(str);  ... //省略的其它语句}

答:不判断str为NULL的情况。 free(str)后,str不置为NULL,可能成为野指针(后面对str的操作可能会导致踩内存)。

问题八:

swap(int* p1,int* p2){  int *p;  *p = *p1;  *p1 = *p2;  *p2 = *p;}

答:swap函数中p是野指针,可能指向系统区,导致程序崩溃。 因此,程序应改为:

swap(int* p1,int* p2){  int p;  p = *p1;  *p1 = *p2;  *p2 = p;}

9 编程题 问题1:判断字符串str2是否在字符串str1中。

#include 
#define OK 1#define ERROR 0
int str_str(const char *str1, const char *str2){ const char *s1 = NULL; const char *s2 = NULL;
if (str1 == NULL) { return (str2 == NULL) ? OK : ERROR; }
if (str2 == NULL) { return OK; }
for (; *str1 != '\0'; str1++) { if (*str1 == *str2) { for (s1 = str1, s2 = str2; ; ) { if (*++s2 == '\0') { return OK; } else if (*++s1 != *s2) { break; } } }  }
return ERROR;}

 
反对 0举报 0 收藏 0 打赏 0评论 0
 
更多>同类资讯
推荐图文
推荐资讯
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报
Powered By DESTOON