C语言面试题总汇精简

备注: 凡是在《程序员面试宝典3》上面有的题目,在此不重复列出了。

 

8. 描述实时系统的基本特性
在特定时间内完成特定的任务,实时性与可靠性


10. 什么是平衡二叉树?
左右子树都是平衡二叉树 且左右子树的深度差值的绝对值不大于1
11. 堆栈溢出一般是由什么原因导致的?
没有回收垃圾资源


14. 写出float x零值比较的if语句。
if(x>0.000001&&x<-0.000001)


2.用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写出C程序。
循环链表,用取余操作做


3.不能做switch()的参数类型是:
switch的参数不能为实型。

华为
1、局部变量能否和全局变量重名?
答:能,局部会屏蔽全局。要用全局变量,需要使用"::"
局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内
2、如何引用一个已经定义过的全局变量?
答:extern
可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错
3、全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?
答:可以,在不同的C文件中以static形式来声明同名全局变量。
可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错
4、语句for( ;1 ;)有什么问题?它是什么意思?
答:和while(1)相同。
5、do……while和while……do有什么区别?
答:前一个循环一遍再判断,后一个判断以后再循环


1static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?


全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
static函数与普通函数作用域不同。仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件

static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;
static
局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;
static
函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝


2.对于一个频繁使用的短小函数,在C语言中应用什么实现,在C++中应用什么实现?
c用宏定义,c++用inline

6.软件测试都有那些种类?
黑盒:针对系统功能的测试 白合:测试函数功能,各函数接口
7.确定模块的功能和模块的接口是在软件设计的那个队段完成的?
概要设计阶段

 

慧通:
什么是预编译
何时需要预编译:
1、总是使用不经常改动的大型代码体。
2、程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。

5、关键字volatile有什么含意?并举出三个不同的例子?
提示编译器对象的值可能在编译器未监测到的情况下改变。

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值(From Memory)而不是使用保存在寄存器里的备份

下面是volatile变量的几个例子:

1) 并行设备的硬件寄存器(如:状态寄存器)

2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

3) 多线程应用中被几个任务共享的变量

这个多用在嵌入式开发中,一般场合不需要使用。


2.交换两个变量的值,不使用第三个变量。即a=3,b=5,交换之后a=5,b=3;
有两种解法, 一种用算术算法, 一种用^(异或)
a = a + b;
b = a - b;
a = a - b;
or
a = a^b;// 只能对int,char..
b = a^b;
a = a^b;


8.类的静态成员和非静态成员有何区别?
类的静态成员每个类只有一个,非静态成员每个对象一个


9.纯虚函数如何定义?使用时应注意什么?
virtual void f()=0;
是接口,子类必须要实现

 

 

 
常用运算符优先级:

 

 

C语言基本数据类型

 数据类型修饰符

signed:有符号    unsigned:无符号

short:短型          long:长型

在基本的数据类型前可以添加修饰符,以改变基本类型的意义。

        unsigned和signed只用于修饰char和int,且signed修饰词可以省略。当用unsigned修饰词时,后面的类型说明符可以省略。

例如:signed int n; //与“int n;”等价

    signed char ch; //与“char ch;”等价

    unsigned int n; //与“unsigned n;”等价

    unsigned char ch; //与“unsigned ch;”等价

        short只用于修饰int,且用short修饰时,int可以省略。

              即:short int n; //与“short n;”等价

        long只能修饰int和double。当用long修饰int时,int可以省略。

              即:long int n; //与“long n;”等价

           int和unsigned int 类型占用一个机器一个字(word)的字节。在16位操作系统上,它们占用2个字节;在32位操作系统上,它们占用4个字节。用sizeof(数据类型)可以确定某数据类型的字节长度。

基本的数据类型及其表示范围,可参见图:

 

 

 字符串常见算法

1 怎样将整型数转化为字符串数,并且不用函数itoa?

#include
#include
int  main(void)
{
int num=12345,j=0,i=0;
chartemp[7],str[7];
while(num)
{temp[i]=num%10+'0';
i++;
num=num/10;
}
 
temp[i]=0;
printf("temp=%s\n",temp);
i=i-1;
printf("temp=%d\n",i);
while(i>=0)
{
  str[j]=temp[i];
  j++;
  i--;
}
str[j]=0;
printf("string=%s\n",str);
return0;
}






2 编程实现字符串数转化成整数的办法

enum Status {kValid = 0, kInvalid};
int g_nStatus = kValid;  //非法输入标识符,非法输入则返回0

///
// Convert a string into an integer
///
int StrToInt(const char* str)            
{
      g_nStatus = kInvalid;    //最开始设置为无效(0),判读是否为非法输入,和有“0”输入情况下的返回判断;如果的确是0,则在后面更改为最初的值kValid。
      long long num = 0;

      if(str != NULL && *str != '\0')
      {

            // the first char in the string maybe '+' or '-'  预处理第一个字符“+ -号”
            bool minus = false;
            if(*str == '+')
                  str ++;
            else if(*str == '-')
            {
                 str ++;
                  minus = true;
            }

	    if (*str != '\0')
            {

             num = StrToIntCore(str, minus);   //对字符串看开始处理

	    }
        }
   return (int)num;
}



long long StrToIntCore(const char * digit, bool minus);
{
	long long num=0;

            // the remaining chars in the string
            while(*digit != '\0')
            {
                  if(*digit >= '0' && *digit <= '9')      //如果是数字字符串,则开始处理了。
                  {     int flag = minus ? -1 : 1;
                        num = num * 10 + flag * (*digit - '0');

                        // overflow  
                        if((!minus && num > 0x7FFFFFFF)|| (minus && num < (signed int )0x80000000))
                        {
                              num = 0;
                              break;
                        }

                        digit ++;
                  }
                  // if the char is not a digit, invalid input    如果不是字符串,则是非法输入,直接返回num,且这是g_nStatus为无效值kInvalid(1)
                  else
                  {
                        num = 0;
                        break;
                  }
            }// while(*digit != '\0')

            if(*digit == '\0')
            {
                  g_nStatus = kValid; //如果是数字字符(包括只含0的情况),直接返回num,且这是g_nStatus为kValid依然为原来的值kValid(0)。

                 
            }
      
      return num;
}




3 编程实现函数strcpy,不调用C/C++的字符串库函数


char *strcpy(char *strDest, const char *strSrc)
{
assert((strDest!=NULL)&&(strSrc!=NULL));
char *address=strDest;
while((*strDest++=*strSrc++)!='\0');
return address;
}



4 编写一个函数,作用是把一个char组成的字符串循环右移n个


void  LoopMove(char  *pStr, int  steps)
{
int n=strlen(pStr) - steps;
char tmp[MAX_LEN];
memcpy(tmp, pStr+n, steps);
memcpy(pStr+steps, pStr, n);
memcpy(pstr, tmp, steps);
}



5 将一句话里的单词进行倒置,标点符号不倒换。比如一句话“I love csdn." 倒换后变成"csdn.love I"。

#include
#include
using namespace std;
 
int main(void)
{
int j=0, i=0, flag=0, begin, end;
char str[]="i come from tianjin.", temp;
j=strlen(str)-1;
printf("string =%s\n", str);
//第一步是进行全盘翻转,将单词变成.nijnait morf emoc i
while(j>i)
{
  temp=str[i];
  str[i]=str[j];
  str[j]=temp;
  j--;
  i++;
}
 
printf("string = %s\n", str);
i=0;
//第二步进行部分翻转,如果不是空格,则开始翻转单词
while(str[i])
{
 if(str[i]!=' ')
  {
    begin=i;
    while(str[i]&&str[i]!=' ') {i++;}
  i=i-1;
  end=i;
  }
  while(end>begin)
  { temp=str[begin];
    str[begin]=str[end];
    str[end]=temp;
    end--;
    begin++;
    }
i++;
}
printf("string = %s\n", str);
return0;
 
}
 



6 转换字符串格式为原来字符串里的字符+该字符连续出现的个数,例如字符串1444223,转换为11432231(1出现1次,4出现3次,2出现2次.....)

#include
#include
using namespace std;
int main()
{
cout<<"Enterthe numbers "<



7 编程实现字符串比较函数strcmp(char *src, char *sub)

intstr_cmp(char *s1, char *s2)
 {
    while((*s1!='\0')|| (*s2!='\0'))
       {
           if(*s1==*s2)               
     {
                   s1++;
                   s2++;
                 }
         else if(*s1<*s2)
               {
                 return -1;
               }
           else
                   return 1;
      }
return 0;
}


 下面几个题目在《编程之美》上面出现过,但是由于非常经典,而且面试时出现的概率特别大,所以自己要特别拿出来总结。

 

第一题: 1 的数目

给定一个十进制正整数N, 写下从1开始,到N的所有整数,然后数一下其中出现所有“1”的个数,即求f(N)。


例如:
N=2, 写下1,2,。 出现1个1;

N=13, 我们写下:1,2,3,4,5,6,7,8,9,10,11,12,13,出现1个的个数是6.

 

求解函数f(N),即返回1 到N之间出现的 1 的个数,如f(13)=6.

 

[cpp] view plaincopy

  1. ULONGLONG COuntInAteger(ULONGLONG n)    //求解一个数(如11)中1的个数  
  2. {  
  3. ULONGLONG iNum = 0;  
  4. while(n!=0)  
  5. {  
  6.     iNum=iNum+(n%10==1)?1:0;  
  7.     n=n/10;  
  8. }  
  9. return iNum;  
  10. }  
  11.     
  12. ULONGLONG f(ULONGULONG n)              //for循环逐个求解  
  13. {  
  14. ULONGLONG iCount=0;  
  15. for(ULONGLONG i=1;i <= n; i++)  
  16.     {  
  17.         iCount=iCount+COuntInAteger(i);  
  18.     }  
  19. return iCount;  


第二题: 求二进制数中1 的个数

对于一个字节(8bit)的无符号整型变量,求其二进制表示中“1”的个数,要求算法的执行效率尽可能高。

 

[cpp] view plaincopy

  1. int Count(BYTE v)  
  2. {  
  3. int num=0;  
  4. while(v)  
  5.   {  
  6.     if(v % 2 ==1)  
  7.      {  
  8.        num++;  
  9.      }  
  10.     v=v/2;  
  11. }  
  12. return num;  
  13. }  
  14.   
  15. int Count(BYTE v)  
  16. {  
  17. int num=0;  
  18. while(v)  
  19.   {  
  20.     v=v&(v-1);  
  21.     num++;  
  22.    }  
  23. return num;  
  24. }  




第三题: 最大公约数问题

写一个程序,求两个正整数的最大公约数。如果两个正整数都很大,有什么简单的方法吗?

求最大公约数是一个很基本的问题。早在公元前300年左右,欧几里得就在他的著作《几何原本》中给出了高效的解法——辗转相除法。辗转相除法使用到的原理很聪明也很简单,假设用fx, y)表示xy的最大公约数,取k= x/yb = x%y,则x = ky + b,如果一个数能够同时整除xy,则必能同时整除by;而能够同时整除by的数也必能同时整除xy,即xy的公约数与by的公约数是相同的,其最大公约数也是相同的,则有fx, y=fy, y % x)(y > 0),如此便可把原问题转化为求两个更小数的最大公约数,直到其中一个数为0,剩下的另外一个数就是两者最大的公约数。辗转相除法更详细的证明可以在很多的初等数论相关书籍中找到,或者读者也可以试着证明一下。
示例如下:
f
42, 30=f30, 12= f12, 6=f6, 0= 6
【解法一】
最简单的实现,就是直接用代码来实现辗转相除法。从上面的描述中,我们知道,利用递归就能够很轻松地把这个问题完成。
具体代码如下:

[cpp] view plaincopy

  1. int gcd(int x, int y)  
  2. {  
  3.     return (!y) ?  x  :  gcd( y,  x%y ) ;  
  4. }  


【解法二】

在解法一中,我们用到了取模运算。但对于大整数而言,取模运算(其中用到除法)是非常昂贵的开销,将成为整个算法的瓶颈。有没有办法能够不用取模运算呢?

采用类似前面辗转相除法的分析,如果一个数能够同时整除xy,则必能同时整除x-yy;而能够同时整x-yy的数也必能同时整除xy,即xy的公约数与x-yy的公约数是相同的,其最大公约数也是相同的,即fx, y= fx-y, y),那么就可以不再需要进行大整数的取模运算,而转换成简单得多的大整数的减法。

在实际操作中,如果x,可以先交换(x, y)(因为(x, y=y, x)),从而避免求一个正数和一个负数的最大公约数情况的出现。一直迭代下去,直到其中一个数为0
示例如下:
f
42, 30=f30, 12=f12, 18= f18, 12= f12, 6= f6, 6= f6, 0= 6

解法二的具体代码如下:
代码清单2-15

[cpp] view plaincopy

  1. BigInt gcd(BigInt x, BigInt y)  
  2. {  
  3.     if(x < y)  
  4.         return gcd(y, x);  
  5.     if(y == 0)  
  6.         return x;  
  7.     else  
  8.     return gcd(x - y, y);  
  9. }  

 

第四题: 求数组的子数组之和的最大值

写一个程序,求两个正整数的最大公约数。如果两个正整数都很大,有什么简单的方法吗?

 

一个有N个整数元素的一维数组( A[0], A[1], ... , A[n-2], A[n-1]),子数组之和的最大值是什么?(要求子数组的元素是连续的)

例子:有数组( -2, 5, 3, -6, 4,-8, 6),则其子数组之和的最大值为8,其对应的数组为(5,3)

 

解法一:采用直接法,记Sum[i...j],为数组A中从第i到第j之间所有数之和,算出所有Sum,取其最大,代码如下,时间复杂度O(N2):

[cpp] view plaincopy

  1. int maxSum1(int *A, int n)  
  2. {  
  3.     int max = -1;  
  4.     int i, j, sum;  
  5.       
  6.     for(i = 0; i < n; i++)  
  7.     {  
  8.           sum = 0;  
  9.           for(j = i; j < n; j++)  
  10.           {  
  11.                 sum += A[j];  
  12.                 if(sum > max )  
  13.                        max = sum;  
  14.           }  
  15.     }  
  16.       
  17.     return max;  
  18. }  

 

命令行参数

Int main(int argc,char *argv[ ])

argv为指针的指针,argc为整数

char **argv or:char *argv[] or: char argv[][]

main()括号内是固定的写法。

下面给出一个例子来理解这两个参数的用法:

假设程序的名称为prog,

 

当只输入prog,则由操作系统传来的参数为:

argc=1,表示只有一程序名称,argc只有一个元素,

argv[0]指向输入的程序路径及名称:./prog

  

当输入prog    para_1       para_2   ,有2个参数,则由操作系统传来的参数为:

argc=3,表示除了程序名外还有2个参数。

argv[0]指向输入的程序路径及名称。

argv[1]指向参数para_1字符串。

argv[2]指向参数para_2字符串。

 

void main( intargc, char *argv[] )

char *argv[] :argv 是一个指针数组,他的元素个数是argc,存放的是指向每一个参数的指针,

他的第一个元素即argv[0]为编译生成的可执行文件名(包括路径eg:"F:/VC/Ex1/Debug/Ex1.exe")

从二个元素(argv[1])开始,

是每一个参数 int argc表示argv的大小,是实际参数个数+1

其中+1是因为argv[0]是编译后的可执行文件名

 

 

命令行界面的程序,通常都需要输入命令行参数帮助程序执行。假定有一个可执行程序名为test。那么运行该程序的的命令行如下: 
test 
带命令行参数是同一行中的附加项: 
test –c TEST 
其中 –c 和 TEST就是命令行参数。C程序可以将这些附加参数读出来,并为自己所用,比如作为程序运行的条件(经常看到调试参数 –D 就是这么一个)。C程序通过使用main()的参数来读取这些附加参数,下面的repeat.c给出一个读出main参数的例子: 
repeat.c: 

#include 
#include 
int main(int argc, char *argv[]) 
{ 
int count; 
printf("The command line has %darguments:/n", argc - 1); 
for(count = 1; count < argc; count++) 
{ 
printf("%d: %s/n", count,argv[count]); 
} 
printf("/n"); 
//system("PAUSE"); 
return 0; 
} 



这里先解释一下main(int argc,char*argv[])这个函数中两个参数的意义,argc记录的是命令行中输入参数的数目,argv是一个拥有argc个元素的字符串数组,每个元素保存一个命令行中输入的参数。 
编译这个文件为可执行文件repeat: 
gcc repeat.c -o repeat 
按下列方式执行 repeat 程序 
./repeat I "love you" 3 
输出如下: 
The command line has 3 arguments: 
1: I 
2: love you 
3: 3 
在这个例子中,argc的值为4,命令行一共输入了四个参数“./repeat”、“I”、“love you”、“3”。在DOS和UNIX环境下,命令行参数中用””符号表示其是一个字符串,视为一个参数。

 

 

C语言中三个动态内存分配函数

malloc函数 


  原型:extern void *malloc(unsigned int num_bytes);
 
用法:#include
   
   功能:分配长度为num_bytes字节的内存块
   
   说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
   当内存不再使用时,应使用free()函数将内存块释放。
   
   举例:
   // malloc.c
   
  

 #include 
   #include 
   main()
   {
   char *p;
   
   p=(char *)malloc(100);  //后面必须是一个整型数,表示多少字节
   if(p)                                //判断是否为空
      printf("MemoryAllocated at: %x",p);
   else
      printf("NotEnough Memory!\n");
   free(p);
   
   getchar();
   return 0;
   }
  


注意事项:
  第一、malloc 函数返回的是 void * 类型,如果你写成:p = malloc (sizeof(int)); 则程序无法通过编译,报错:“不能将 void* 赋值给 int * 类型变量”。所以必须通过 (int *) 来将 强制转换。 
  第二、函数的实参为 sizeof(int) ,用于指明一个整型数据需要的大小。如果你写成: 
  int* p = (int *) malloc (1); 
  代码也能通过编译,但事实上只分配了1个字节大小的内存空间,当你往里头存入一个整数,就会有3个字节无家可归,而直接“住进邻居家”!造成的结果是后面的内存中原有数据内容全部被清空。 

  

  函数名: calloc

  功 能: 分配主存储器

原型:extern void *calloc(size_t  num,size_t size));
用法:#include
   功能:分配长度为num*size个字节的内存块
   说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
   当内存不再使用时,应使用free()函数将内存块释放。

程序例:
 

 #include 
   #include 
  int main(void)
  {
   char *str = NULL;
   /* allocate memory for string */
   str = calloc(100, sizeof(char));
if(str)                              //判断是否为空
      printf("Memory Allocated at: %x", str);
   else
          printf("NotEnough Memory!\n");
   /* copy "Hello" into string */
   strcpy(str, "Hello");
   printf("String is %s\n", str);
   free(str);
   return 0;
  }

  


   函数名: realloc 
   原型:extern void *realloc(void*mem_address, unsigned int newsize);
   用法:#include
   功能:改变mem_address所指内存区域的大小为newsize长度。
   说明:如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
   当内存不再使用时,应使用free()函数将内存块释放。
   
   举例:
  
   // realloc.c
 

#include
   #include 
   int main()
   {
   char *p;
   p=(char *)malloc(100);
   if(p)                                      //判断是否为空
      printf("Memory Allocated at: %x",p);
   else
          printf("NotEnough Memory!\n");
   
   getchar();
  
   p=(char *)realloc(p,256);
   if(p)                                   //判断是否为空
   printf("Memory Reallocated at: %x",p);
   else
   printf("Not Enough Memory!\n");
   free(p);
   getchar();
   return 0;
   }
  


  三个函数的申明分别是: 
  void* realloc(void* ptr, unsigned newsize); 
  void* malloc(unsigned size); 
  void* calloc(size_t num, size_t sizeOfElement); 
  都在stdlib.h函数库内 
  它们的返回值都是请求系统分配的地址,如果请求失败就返回NULL 
  
  malloc用于申请一段新的地址,参数size为需要内存空间的长度,如: 
  char* p; 
  p=(char*)malloc(20); 
  
   calloc malloc 相似 , 参数 sizeOfElement 为申请地址的单位元素长度 ,numElements 为元素个数 ,
char* p; 
p=(char*)calloc(20,sizeof(char)); 
  这个例子与上一个效果相同  

  realloc是给一个 已经分配了地址的指针重新分配空间, 参数ptr为原有的空间地址,  newsize是重新申请的地址长度 
  如: 
  char* p; 
  p=(char*)malloc(sizeof(char)*20); 
  p=(char*)realloc(p,sizeof(char)*40); 
  
  注意,这里的空间长度都是以字节为单位。 
  
  C语言的标准内存分配函数:malloc,calloc,realloc,free等。 
  malloc与calloc的区别为1块与n块的区别: 
  malloc调用形式为(类型*)malloc(size):在内存的动态存储区中分配一块长度为“size”字节的 连续区域,返回该区域的首地址。 
  calloc调用形式为(类型*)calloc(n,size):在内存的动态存储区中分配n块长度为“size”字节的 连续区域,返回首地址。 
  realloc调用形式为(类型*)realloc(*ptr,size):将ptr内存大小增大到size。 , 为了增加存储区域长度,原分配的存储区可能会进行必要的移动。如果发生移动,原来空间的内容会自动拷贝到新移动的区域内,以保证不会丢失信息。由于这种原因,该函数应返回一个改变后的空间首地址
  free的调用形式为free(void*ptr):释放ptr所指向的一块内存空间。 
  C++中为new/delete函数。
   



指向函数的指针


(一) 用函数指针变量调用函数

  可以用指针变量指向整形变量、字符串、数组、结构体、也可以指向一个函数。一个函数在编译时被分配一个入口地址。这个入口地址就称为函数指针。可以用一个指针变量指向函数,然后通过该指针变量调用此函数。用简单的数值比较为例:

复制代码
 #include 
 #include 
 
  int main()
 {
     int max(int,int);
     int (*p)(int,int);
     int a,b,c;
     p = max;
     scanf("%d,%d",&a,&b);
    c = (*p)(a,b);
     printf("a=%d,b=%d,max=%d\n",a,b,c);
    return 0;
 }

int max(int x,int y)
 {
     int z;
     if(x>y) z = x;
     else z = y;
     return(z);
 }
复制代码

  第7行:int (*p)( int,int ); 用来定义 p 是一个指向函数的指针变量,该函数有两个整形参数,函数值为整形。

  赋值语句 p = max ; 作用是将函数 max 的入口地址赋给指针变量p。和数组名代表数组首元素地址类似,函数名代表该函数的入口地址。这时 p 就是指向函数 max 的指针变量,此时 p 和 max都指向函数开头,调用 *p 就是调用 max 函数。但是p作为指向函数的指针变量,它只能指向函数入口处而不可能指向函数中间的某一处指令处,因此不能用 *(p + 1)来表示指向下一条指令。

  注意:

  (1) 指向函数的指针变量的一般定义形式为:

  数据类型 (*指针变量名)(函数参数列表)

  这里数据类型就是函数返回值的类型

  (2) int (* p) ( int,int ); 它只是定义一个指向函数的指针变量 p, 它不是固定指向哪一个函数的,而只是表示定义这样一个类型的变量,它是专门用来存放函数的入口地址的。在程序中把哪一函数(该函数的值应该是整形的,且有两个整形参数)的地址赋给它,他就指向哪一个函数。在一个函数中,一个函数指针变量可以先后指向同类型的不同函数。

  (3) p = max; 在给函数指针变量赋值时,只需给出函数名而不必给出函数参数,因为是将函数的入口地址赋给 p ,而不涉及 实参和形参的结合问题,不能写成 p = max(a,b);

  (4) c = (*p)(a,b) 在函数调用时,只需将( *p ) 代替函数名即可,后面实参依旧。

  (5) 对于指向函数的指针变量,像 p++ ,p+n.....是无意义的。


(二) 用指向函数的指针作为函数参数

  函数指针变量通常的用途之一就是把指针作为参数传递到其他函数。

  函数的参数可以是变量、指向变量的指针变量、数组名、指向数组的指针变量,也可以是指向函数的指针也可以作为参数,以实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。

  void sub ( int ( *x1) (int), int (*x2) (int,int) )

    {

      int a,b,i,j;

      a = (*x1)(i);      /* 调用 f1 函数 */

      b = (*x2)(i)(j);    /* 调用 f2 函数 */

    }

  如果实参为两个 函数名 f1 和 f2. 在函数首部定义x1、x2为函数指针变量,x1指向的函数有一个整形形参,x2指向的函数有两个形参。i 和 j 是函数f1 和 f2所要的参数。函数sub的形参 x1、x2(指针变量)在函数 sub 未被调用时并不占用内存单元,也不指向任何函数。在sub被调用时,把实参函数 f1 和 f2的入口地址传给形式指针变量 x1 和 x2.

  既然在 sub 函数中要调用 f1 和 f2 函数,为什么不直接调用f1 和 f2而要用函数指针变量呢? 确实,如果只是用到f1 和 f2 函数,完全可以在sub函数中直接调用f1 和 f2,而不必设指针变量 x1 和 x2。 但是,如果在每次调用sub时,调用的函数不是固定的,下次是f3 和 f4,再是f5 和 f6...这时用指针变量就比较方便了。



  返回指针的函数

每个函数可返回一个值,返回值可以是char、int、float、double等类型,当将返回值类型设置为void时,表示函数没有返回值。在C语言中,还允许一个函数的返回值是一个指针(即地址),这种返回指针的函数称为指针型函数。

定义指针型函数的形式如下:

 
  
  1. 类型说明符 *函数名(形参表)  
  2. {  
  3.    …      /*函数体*/  

其中函数名之前加了"*"号表明这是一个指针型函数,即返回值是一个指针。类型说明符表示了返回的指针值所指向的数据类型。一般用这种函数返回一个字符串常量的首地址。

编写一个函数,用于将阿拉伯数字表示的月份转换为对应的英文名称。函数一次只能返回一个值,若要返回一个字符串(由多个字符组成),用前面已介绍的方法可通过函数的形参返回多个字(包括一个字符串)。例如,用以下的函数头:

 
  
  1. void cmonth(int month, char s[]) 

要调用以上形式的函数,首先要定义一个数组,再将数组作为实参传给函数,最后将函数处理的结果用另一个语句输出。使用类似下面的程序:

 
  
  1. char s[20];  
  2. cmonth(5, s]);  
  3. printf("月份:%2d-->英文名称:%s\n",5,s); 

如果函数能返回字符串,则可以使用以下方式调用函数,并输出返回值:

 
  
  1. printf("月份:%2d-->英文名称:%s\n",i,cmonth(i)); 

编写指针型函数可返回字符串的首地址,下面的程序演示指针型函数的编写方法。

【程序9-27】

 
  
  1. #include <stdio.h>  //头文件  
  2. #include <stdlib.h> 
  3.  
  4. char *cmonth(int month);//函数声明  
  5.  
  6. int main()  
  7. {  
  8.     int i;  
  9.  
  10.     printf("输入月份数字:");  
  11.     scanf("%d",&i); //输入月份  
  12.     printf("月份:%2d-->英文名称:%s\n",i,cmonth(i));  
  13.  
  14.     system("pause");  
  15.     return 0;  
  16. }  
  17.  
  18. char *cmonth(int month)//自定义函数  
  19. {  
  20.     char *str_month[]={//初始化  
  21.                     "Illegal Month",  
  22.                     "January",  
  23.                     "February",  
  24.                     "March",  
  25.                     "April",  
  26.                     "May",  
  27.                     "June",  
  28.                     "July",  
  29.                     "August",  
  30.                     "September",  
  31.                     "October",  
  32.                     "November",  
  33.                     "December"  
  34.                    };  
  35.     char *p;  
  36.  
  37.     if(month>=1 && month<=12)       //判断是否合法  
  38.         p=str_month[month];  
  39.     else  
  40.         p=str_month[0];  
  41.     return p;  

执行这段程序,按照提示输入月份数字,得到如下结果,如图9-53所示。

在该程序中,定义了函数cmonth(),该函数需要一个整型变量作为实参,返回一个字符型指针。在函数体内部定义指针数组,数组中的每个指针指向一个字符串常量。然后,判断实参month是否合法,若不合法则将第一个元素赋值给字符指针变量p,这样,指针变量p中的值就与指针数组中第一个元素中的值相同,即指向字符串常量"Illegal Month",如图9-54所示。当函数参数month为1~12之间的一个值时,即可使字符指针指向对应的字符串常量(变量p中保值的值是一个地址)。

main()函数中,在printf()函数输出列表中包括cmonth()函数的返回值(其返回值是一个字符串的首地址),printf()函数的格式字符"%s"从该首地址开始输出字符串。

C语言面试题总汇精简_第1张图片 
图9-53  执行结果
C语言面试题总汇精简_第2张图片 
图9-54  用指针操作字符串
【责任编辑: 云霞 TEL:(010)68476606】



指针与数组

1  通过指针引用数组元素

int a[10];

int *p;

p=a;(p=&a[0])


P+i , a+i 都是&a[i];

*(P+i) ,*( a+i)都是a[i];

p[i]=*(p+i)=a[i];


例子:求给定10个整型数中的最大值

#include
int main()
{int a[10]={5,3,6,1,7,4,8,2,19,100};
int i,max,*p;
p=a;
max=*p;
for(i=1;i<=10;i++,p++)
{if(*p>max)max=*p;
}
printf("max=%d\n",max);
return 0;
}



2  数组名作函数参数

常规用法:

main()

{ int a[10];

......

......

f(a,10);

......

......

}


f(int array[], int n)

{

......

......

}


上述:实际上是将数组的首地址传给形参,这样实参数组与形参数组共占同一段内存空间。

因而也可以使用指针变量代替数组名来进行地址的传送。

还可以有以下几种对应关系:

1   实参用数组名,形参用指针变量:

int a[10];


f(a, 10);


f( int *p,  int n)

{

......

......

}


2   实参用指针变量,形参用数组名;

int  a[10], *pa;

pa=a;

......


f(pa, 10);

......

f(int b[], int n)

{

......

}


3  形参和实参都用指针变量

int a[10], *pa;

pa = a;

....

f(pa, 10);

.....


f(int *p, int n)

{

......

}



例子:用指针对数组进行从小到大排序

#include

sort(int *p,int n)
{int i,j,temp;
for(i=0;i*(p+j))
		{temp=p[i];
                 *(p+i)=*(p+j);
                 *(p+j)=temp;
                }
return 0;
}

int main()
{int a[10],*pa,i;
pa=a;
printf("Input 10 integer:");
for(i=0;i<10;i++)
	scanf("%d",pa++);
pa=a;
sort(pa,10);
for(i=0;i<10;i++)

	printf("%d ",*pa++);
return 0;
}



3  多维数组与指针

例如:

int a[3][4]={{0,1, 2, 3},{10, 11, 12, 13},{20, 21, 22, 23}};

a[i]+j 是&a[i][j];

*(a[i]+j) 是 a[i][j];

a+i可以看做是指向a[i]的指针

*(a+i)=a[i];(依然是地址值,即a[i][0]的地址,一维数组)

*(*(a+i) + j)=a[i][j];


用指向数组元素的指针变量引用数组元素

#include
int main()
{int a[3][4]={{0,1,2,3},{10,11,12,13},{20,21,22,23}};
int *p,i,j;
p=a[0];
printf("\n");
for(i=0;i<3;i++)
  { for(j=0;j<4;j++)
        printf("%4d",*p++);
        printf("\n");
  }
return 0;
}




4  指针与字符串

#include
int main()
{char *ps="How do you do!";
printf("%s\n",ps);
return 0;
}


%s表示输出一个字符串,由字符串型指针变量ps给出字符串的起始地址,则系统从该地址开始输出字符,直到遇到字符串结束标志'\0'为止。




你可能感兴趣的:(面试,个人百科,C语言)