答:预编译又称预处理 , 就是做些代码文本的替换工作。# 开头的指令,比如拷贝 #include 包含的文件代码,#define 宏定义的替换 , 条件编译等,就是为编译做的预备工作的阶段。
C提供的预处理功能主要有以下三种:
1 )宏定义。#define
2 )文件包含。#include。
该指令指示编译器将xxx.xxx文件的全部内容插入此处。
3 )条件编译。#ifdef/#ifndef, #endif, #if/#else
何时需要预编译:
1 )总是使用不经常改动的大型代码体。
2 )程序由多个模块组成,所有模块使用一组标准的包含文件和相同的编译选项。
预处理 ——> 编译 ——> 汇编 ——> 链接
预处理:在高级语言源程序中插入所有用#include命令指定的文件和用#define声明指定的宏。
编译:将预处理后的源程序文件编译生成相应的汇编语言程序。
汇编:由汇编程序将汇编语言源程序文件转换为可重定位的机器语言目标代码文件。
链接:由链接器将多个可重定位的机器语言目标文件以及库例程(如printf()库函数)链接起来,生成最终的可执行目标文件(机器码)。
#include
与 #include “file.h”
的区别?答:前者从标准库路径寻找file.h;
后者从当前工作路径寻找file.h。
答:防止该头文件被重复引用。
答:
#ifdef __cplusplus
printf(“c++”);
#else
printf(“c”);
#endif
答:只要遇到 #error 就会跳出一个编译错误,其目的就是保证程序是按照你所设想的那样进行编译的。
答:#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
答:#define SWAP(a,b) (a)=(a)+(b); (b)=(a)-(b); (a)=(a)-(b)
答:#define MIN(A, B) ((A)>=(B)) ? (B) : (A)
答:#define NTBL(tab) (sizeof(tab) / sizeof(tab[0]))
答:
#define BITS_CLR(v, n) v &= ~(1u<
答:
带参宏 | 优点 | 1)参数不存类型转换和运算问题,因为带参的宏只是替换; 2)不占用运行时间(无内存、现场保护、值传递、返回等) |
缺点 | 1)宏使用次数多,程序增加很多; 2)占编译时间 |
|
带参函数 | 优点 | 1)函数调用次数多,程序增加很少; 2)不占编译时间 |
缺点 | 1)参数存在类型转换和运算问题; 2)调用占运行时间(分配内存、现场保护、值传递、返回) |
答:为 数据类型 取 别名
答:typedef要比#define要好,特别是在有指针的场合。请看例子:
typedef char* pStr1;
#define pStr2 char*
pStr1 s1, s2;
pStr2 s3, s4;
在上述的变量定义中,s1、s2、s3都被定义为char *,而s4则定义成了char,不是我们所预期的指针变量,根本原因就在于#define只是简单的字符串替换而typedef则是为一个类型起新名字。上例中define语句必须写成 pStr2 s3, *s4; 这这样才能正常执行。
答:
while(1) //方案1
{
;
}
for( ;1 ;) //方案2
{
;
}
Loop:
…
goto Loop; //方案3
do……while
和while……
有什么区别?答:前一个循环一遍再判断,后一个判断以后再循环。
switch()
的参数类型答:实型
a) 一个整型数(Aninteger)?
b) 一个指向整型数的指针(Apointer to an integer)?
c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer)?
d) 一个有10个整型数的数组(An array of 10 integers)?
e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers)?
f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers)?
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument andreturns an integer)?
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argumentand return an integer )
答:
a) int a;
b) int *a;
c) int **a;
d) int a[10];
e) int *a[10];
f) int (*a)[10];
g) int (*a)(int );
h) int (*a[10])(int );
答:
const char* p = "hello"; //指向 "字符串常量"
p[0] = 'X'; //错误! 想要修改字符串的第一个字符. 但是常量不允许修改
p = p2; //正确! 让p指向另外一个指针.
char* const p = "hello"; //指向字符串的" 常量的指针"
p[0] = 'X'; //正确! 允许修改字符串, 因为该字符串不是常量
p = p2; //错误! 指针是常量, 不许修改p的指向
char const * 和 const char* 是一样的. const 的位置在char左边还是右边都一样.
常量指针的const应当写在 *星号的右边.
指向常量字符串的常量指针的写法是 const char* const p = "xx";
要2个const
答:野指针就是 指针指向的位置是不可知的。
原因:
1、指针变量未初始化
2、指针释放后之后未置空(指针所指向的变量 在指针之前被销毁)
3、指针操作超越变量作用域
规避:
1、初始化时置 NULL
2、释放时置 NULL
答:
数组 在静态存储区被创建(如全局数组),或 在栈上被创建。
指针 可以随时指向任意类型的内存块。
(1)修改内容上的差别
char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; // 注意p 指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误,运行时错误
(2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p), p 为指针得到的是一个 指针变量的字节数,而不是p所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
char a[] = “hello world”;
char *p = a;
printf("%d\r\n", sizeof(a)); // 12 字节
printf("%d\r\n", sizeof(p)); // 4 字节
计算数组和指针的内存容量
void Func(char a[100])
{
printf("%d\r\n", sizeof(a));; // 4 字节而不是100 字节
}
答:
BOOL : if( !a ) or if( a )
int : if(a == 0)
float : const float EXP = 0.000001;
if((a >= -EXP) && (a <= EXP))
pointer : if(a != NULL) or if(a == NULL)
指针有两个属性:指向变量/对象的 地址 和 长度,但指针只存储地址, 长度则取决于指针的类型;编译器根据指针的类型从指针指向的地址向后寻址,指针类型不同则寻址范围也不同.
比如:
int 从指定地址向后寻找4字节作为变量的存储单元
double 从指定地址向后寻找8字节作为变量的存储单元
void 即“无类型”,void *则为“无类型指针”,可以指向任何数据类型。
void指针 可以指向任意类型的数据,即可用任意数据类型的指针对void指针赋值。
例如:
int *pint;
void *pvoid; //它没有类型,或者说这个类型不能判断出指向对象的长度
pvoid = pint; //只获得变量/对象地址而不获得大小,但是不能 pint = pvoid;
如果要将pvoid赋给其他类型指针,则需要强制类型转换。如:
pint = (int *)pvoid; //转换类型也就是获得指向变量/对象大小
void指针不能复引用(即取内容的意思)
因为void指针只知道指向变量/对象的起始地址,而不知道指向变量/对象的大小(占几个字节)所以无法正确取内容。在实际的程序设计中,为迎合ANSI标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码:
void*pvoid;
(char*)pvoid++; //ANSI:正确;GNU:正确
(char*)pvoid+=1; //ANSI:错误;GNU:正确
答:
#include
void change(int * a, int &b, int c)
{
c = *a;
b = 30; //引用
*a = 20;
}
int main ( )
{
int a=10, b=20, c=30;
change(&a,b,c);
printf(“%d,%d,%d,”,a,b,c);
return 0;
}
A 20, 30, 30
B 10, 20, 30
C 20, 30, 10
D 10, 30, 30
答:
1)在函数内,修饰变量为静态局部变量,仅初始化一次,函数调用中维持其上次值。
2)在文件内,修饰变量为本地静态全局部变量,仅初始化一次,仅本文件内使用,其他文件不可用。
3)在文件内,修饰函数为本地静态函数,仅本文件内使用,其他文件不可用。
答:
1)static局部变量 与 普通局部变量 的作用域相同,都是函数内,但存储类型不同。
2)static局部变量 是 静态存储方式,生存期是一直存在;
3)普通 局部变量 是 非静态存储方式, 生存期是本次函数调用,函数返回则释放。
答:
1)static全局变量 与 普通全局变量 都是 静态存储方式,但作用域不同。
2)static全局变量作用域 限一个源文件内使用;
3)普通 全局变量作用域 可多个源文件内使用。
答:static函数 与 普通函数 作用域不同。static函数仅本文件内使用。普通函数 可多个文件内使用。
总结:
static对 局部变量 改变生存期;
static对 全局变量 改变作用域;
static对 函数 改变作用域;
const的意思是只读,不可改变的。
作用:定义常量、修饰函数参数、修饰函数返回值三个作用。
定义:const修饰的数据类型是指常类型,常类型的变量或对象的值是不能被更新的。
目的:const 推出的初始目的是为了取代预编译指令,消除它的缺点,同时继承它的优点。
const int a; //a是一个 常整型数
int const a; //a是一个 常整型数
const int *a; //a是一个指向 常整型数 的指针(整型数是不可修改,但指针可以)
int * const a; //a是一个指向整型数的 常指针(指针指向的整型数是可以修改的,但指针是不可修改的
const int * const a; //a是一个指向 常整型数的 常指针(指针指向的整型数 和 指针 都不可修改)
答:被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。
(1)定义const常量,具有不可变性。
例如:const int Max=100; int Array[Max];
(2)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。
如(1)中,如果想修改Max的内容,只需要:const int Max=you want;
即可!
(3)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。
例如:void f(const int i) { .........}
编译器就会知道i是一个常量,不允许修改;
(4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。
如(2),如果在函数体内修改了i,编译器就会报错;
(5)可以节省空间,避免不必要的内存分配。
例如:
#define PI 3.14159 //常量宏
const double PI = 3.14159; //此时并未将Pi放入RAM中 ......
double a=PI; //此时为Pi分配内存,以后不再分配!
double b=PI; //编译期间进行宏替换,分配内存
double c=Pi; //没有内存分配
double d=PI; //再进行宏替换,又一次分配内存!
const 定义常量从汇编的角度来看,只是给出了对应的内存地址;
#define 给出的是立即数;
所以,const 定义的常量在程序运行过程中只有一份拷贝,
而 #define 定义的常量在内存中有若干个拷贝。
(6)提高了效率。
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
答:volatile是易改变的意思,定义volatile的变量编译器就不会去优化它了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
下面是volatile变量的几个例子:
1)并行设备的硬件寄存器(如:状态寄存器)
2)一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3)多线程应用中被几个任务共享的变量
volatile关键字是一种类型修饰符。
作用:防止编译器对代码进行优化。
使用地方:
1)中断服务程序中修改的供其它程序检测的变量需要加volatile;
2)多任务环境下各任务间共享的标志应该加volatile;
3)存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现, 2 中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
答:是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
答:是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
答:这段代码的有个恶作剧。这段代码的目的是用来返指针ptr指向值的平方,但是,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!
正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
(1)const含义是“请做为常量使用”,而并非“放心吧,那肯定是个常量”。
(2)volatile的含义是“请不要做自以为是的优化,这个值可能变掉的”,而并非“你可以修改这个值”。
(1)const只在编译期有用,在运行期无用
const在编译期保证在C的“源代码”里面,没有对其修饰的变量进行修改的地方(如有则报错,编译不通过),而运行期该变量的值是否被改变则不受const的限制。
(2)volatile在编译期和运行期都有用
在编译期告诉编译器:请不要做自以为是的优化,这个变量的值可能会变掉;
在运行期:每次用到该变量的值,都从内存中取该变量的值。
补充:
编译期 -- C编译器将源代码转化为汇编,再转化为机器码的过程;
运行期 -- 机器码在CPU中执行的过程。
答:
(1)“编译器一般不为const变量分配内存,而是将它保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作。”
(2)volatile的作用是“告诉编译器,随时可能发生变化的,每次使用它的时候必须从内存中重新取出”。
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6)? printf("> 6") : printf("<= 6");
}
答案:“>6”。
原因:当表达式中存在 有符号类型 和 无符号类型 时,所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。
int a = 5, b = 7, c;
c = a+++b;
答案:合法,但规范不好。这段代码持行后a = 6, b = 7, c = 12。
#include
main()
{
int a,b,c,d;
a=10;
b=a++;
c=++a;
d=10*a++;
printf(“b,c,d:%d,%d,%d”,b,c,d);
return 0;
}
答:10,12,120
#include
int inc(int a)
{
return(++a);
}
int multi(int *a, int *b, int *c)
{
return(*c = *a * *b);
}
typedef int(FUNC1)(int in);
typedef int(FUNC2) (int*,int*,int*);
void show(FUNC2 fun,int arg1, int*arg2)
{
FUNC1 p = &inc;
int temp = p(arg1);
fun(&temp, &arg1, arg2);
printf(“%d\n”, *arg2);
}
main()
{
int a;
show(multi, 10, &a);
return 0;
}
答:110
说明:以下代码是把一个字符串倒序,如“abcd”倒序后变为“dcba”
#include”string.h”
main()
{
char* src = ”hello,world”;
int len = strlen(src);
char* dest = (char*)malloc(len);
char* d = dest;
char* s = src[len];
while(len–- != 0) d++ = s–-;
printf(“%s”,dest);
return 0;
}
答:方法1:
int main()
{
char* src = “hello,world”;
int len = strlen(src);
char* dest = (char*)malloc(len+1); //要为\0分配一个空间
char* d = dest;
char* s = &src[len-1];//指向最后一个字符
while(len–- != 0) *d++ = *s–-;
*d = 0; //尾部要加\0
printf(“%s\n”,dest);
free(dest); //使用完,应当释放空间,以免造成内存汇泄露
return 0;
}
方法2:
#include
#include
int main(void)
{
char str[]="hello,world";
int len=strlen(str);
char t;
for(int i=0; i <= (len/2); i++)
{
t=str[i];
str[i]=str[len-i-1];
str[len-i-1]=t;
}
printf("%s",str);
return 0;
}
int a[60][250][1000], i, j, k;
for(k=0; k<=1000; k++)
for(j=0; j<250; j++)
for(i=0; i<60; i++)
a[i][j][k]=0;
答案:把循环语句内外换一下
#define Max_CB 500
void LmiQueryCSmd(Struct MSgCB * pmsg)
{
unsigned char ucCmdNum;
......
for(ucCmdNum=0; ucCmdNum
答案:死循环
答:
1)全局变量储存在静态数据区,局部变量在堆栈中。
2)全局变量的作用域是整个函数,局部变量的作用域是声明该变量的函数
答:能,局部会屏蔽全局。要用全局变量,需要使用"::"
答:可以,在不同的C文件中以static形式来声明同名全局变量。只能有一个C文件中对此变量赋初值,此时连接不会出错。
答:用extern关键字 在 头文件 中 声明全局变量。
答:
1)栈区(stack)----由编译器自动分配释放,存放函数的参数值,局部变量等。
2)堆区(heap)----一般由程序员分配释放。分配使用new和malloc,释放使用deleted和free
3)全局区(静态区)(static)----全局变量和静态变量是存放在一块的。初始化的在一块区域, 未初始化存放在另一块区域(BSS)。
4)常量区----存放常量字符串。
5)程序代码区----存放函数体的二进制代码。
例子程序:
int a=0; //全局初始化区
char *p1; //全局未初始化区
main()
{
int b; //栈
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;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。
答:队列先进先出,栈后进先出
答:
1)申请方式。
stack:由系统自动分配。
例如:声明在函数中一个局部变量int b; 系统自动在栈中为b开辟空间
heap:需要程序员自己申请,并指明大小。在c中malloc函数
如:p1 = (char*)malloc(10); //在C++中用new运算符。注意p1本身是在栈中的。 `
2)申请效率的比较。
栈: 由系统自动分配,速度较快。但程序员是无法控制的。
堆: 是由malloc/new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
3)申请大小的限制。
栈:系统预先规定好的.事先配置好。栈获得的空间较小。
堆:程序员自己定义堆获得的空间比较灵活,也比较大。
4). 堆和栈中的存储内容。
栈:存储函数 局部临时变量、现场的保护等。
堆:堆中的具体内容由程序员安排。
答:
1.没有回收垃圾资源;
2.层次太深的递归调用
char str[20]="0123456789";
int a=strlen(str); //a=10; strlen计算字符串的长度,以'\0'为字符串结束标记(长度不包含'\0')。
int b=sizeof(str); //b=20; sizeof计算数组str[20] 所占的内存空间的大小,不受里面存储的内容影响。
char* ss = "0123456789";
sizeof(ss)
结果 4 ——> ss是指向字符串常量的字符指针,sizeof 获得的是一个指针的之所占的空间。sizeof(*ss)
结果 1 ——> *ss是第一个字符 '0' 所占的内存空间,是char类型的,占了1位strlen(ss)
结果 10 ——> 如果要获得这个字符串的长度,则一定要使用 strlen
sizeof结构体为结构体中定义的数据类型的总的空间(注意字节对齐)。
sizeof对union为union中定义的数据类型的最大数据类型的大小。
void UpperCase( char str[] ) //将 str 中的小写字母转换成大写字母
{
for(int i=0; i
答:
1)、函数内的sizeof有问题。根据语法,sizeof如用于数组,只能测出静态数组的大小,无法检测动态分配的或外部数组大小。
2)、函数外的str是一个静态定义的数组,因此其大小为6,函数内的str实际只是一个指向字符串的指针,没有任何额外的与数组相关的信息,因此sizeof作用于上只将其当指针看,一个指针为4个字节,因此返回4。
struct A
{
long a1;
short a2;
int a3;
int *a4;
};
请问在64位编译器下用sizeof(struct A)计算出的大小是多少? A
A 24
B 28
C 16
D 18
答 :O(n^2)
/**
******************************************************************************
* @brief 冒泡排序加强版 函数
* @param *ary 数据指针
* @param len 数据长度
* @param dir 排序方向(1--降序;0--升序)
* @return None
* @note 升降排列通用
******************************************************************************
*/
#define BubbleType int //元素类型
void BubbleSortPlus(BubbleType *ary, unsigned char len, unsigned char dir)
{
BubbleType tmp;
unsigned char i, j;
len--; //自减1。注意不能少
for(i=0; i
A 9,5,4,2
B 10,5,3,2
C 9,6,2
D 20,10,5,3,2
int BinSearch(const int *Array, int low, int high, int target)
{
int mid;
while(low <= high)
{
mid = low + (high - low) / 2;
if(target == Array[mid]) return mid;
else if(target < Array[mid]) high = mid - 1;
else if(target > Array[mid]) low = mid + 1;
}
return -1;
}
A O(i)
B O(1)
C O(n)
D O(i-1)
A 10,5
B 9,4
C 8,3
D 7,6