C语言面试笔试

1、运算符和表达式

1.1自增自减运算符++与–

n++:表示先返回n,再让n+1=>n;
++n:表示先让n+1=>n,再返回n;
n–:表示先返回n,再让n-1=>n;
–n: 表示先让n-1=>n,再返回n;

例如n=2,表达式n++是先返回n,即2,再n自增1变为3,表达式++n是先n自增1变为3,再返回n,即3.

注意:

  • 1

++、–运算可以提高程序的执行效率。这是因为++、–只需要一条机器指令就可以完成,而n=n+1要对应3条机器指令;

  • 2

自增自减运算符的对象只能是简单变量,不能是常数,或带有运算符的表达式,即5++、–(a+b)是错误的;

贪心法


符号指的是程序的一个基本组成单元,起作用相当于一个句子中的单词,在C编译器解释表达式符号时,它在移动到下一个符号之前在单个符号中包含尽可能多的字符,称之为贪心法
比如a++++b 这句话被解释为(a++)++b(所以表达式错误);
a+++b++被解释为(a++)+(b++)表达式正确

#include <stdio.h>
int main()
{
int x;
scanf("%d",&x);
if (x++>5)
printf("%d\n",x);
else
printf("%d\n",--x);//注意这行代码
}

上面是输入4,得到4;

#include <stdio.h>
int main()
{
int x;
scanf("%d",&x);
if (x++>5)
printf("%d\n",x);
else
printf("%d\n",x--);
}

上面的输入4得到5;

数组

一维数组和指针的区别

在C语言中,规定数组名代表数组的首元素的地址,也就是说,数组名就具有地址的概念,而且是一个地址常量,因此可以将数组名(即在内存中存放该数组的首地址)赋给指针;

特别注意数组名a代表的是该数组首元素的地址,而不是数组a的首地址,a与&a[0]的含义相同,如“a=&a[0]”返回真,是正确的比较;

&a表示整个数组的首地址,但是在执行语句printf("%x,%x\n",a,&a);时输出的a和&a是相同的,那么两者有什么区别呢?其中区别主要是步长的不同,a+i=a+sizeof(int),其步长为sizeof(int),即一个数组元素的长度,而&a+i=&a+i(a数组的大小),其中步长为a数组的大小

为了清楚地说明&a和a的差别,这里采用地址分级的概念,数组元素的地址称为一级地址(其值可以赋给一级指针),而一级地址的地址称为二级地址,以此类推,不同级别的地址是不能比较的,因为对应的步长不同。一维数组名a是一级地址,而&a中加了一个取地址运算符升级为二级地址;

sizeof的使用
指针变量的sizeof指针变量的sizeof等于计算机内部地址总线的宽度,所以在32位计算机中一个指针变量的返回值必定是4(注意结果是以字节为单位)
数组的sizeof,对数组做sizeof运算等效于对其元素类型做sizeof的结果乘以数组元素的个数,即sizeof返回整个数组在内存中占用的内存字节数

char a[]="abc";
int b[3];
sizeof(a);	//结果为4,字符末尾还存在一个'\0'结尾符
sizeof(b);//结果为3×4=12(依赖于int的长度)

二维数组

二维数组元素的引用方式如下:
数组名[下标表达式1][下标表达式2]
其中,下标表达式可以是整型常量或整型表达式,注意不能为变量或者const
对于给了数组部分元素赋初值的,其余自动赋值为0;

int a[3][4]={{1,2,3,4},{5,6,7,8}};

注意允许行,但是不能允许省略列,二维数组在初始化时候必须指定列长度。

数组名a代表的是该二维数组首元素a[0]的首地址,即a与&a[0]的含义相同.也就是说a与&a[0]的含义相同,“a=&a[0]”返回真,是正确的比较。其中a[0]又和&a[0][0]的含义相同,所以a==&&a[0][0].

二维数组名是一个二级地址(例如**a的结果为a[0][0]),三维数组名是一个三级地址;
&a是整个二维数组的首地址,为三级地址,所以“a==&a”的比较是错误的;
其实,我们只要记住数组名a代表的是首元素的地址,二维数组降解为一维数组来看;

用一级指针访问二维数组元素

#include <stdio.h>
#include <malloc.h>
void main()
{

	int i;
	int a[3][2]={{0,1},{2,3},{4,5}};
	int *p=a[1];
	for (i=0;i<2;i++)
		printf("%d",*p++);
}

输出2 3;

若有定义int a[3][4] ,不能表示a[1][1]的是()
A、*(&a[0][0]+5)
B、*(*(a+1)+1)
C、*(&a[1]+1)
D、*(a[1]+1)

正确答案C
C选项表示的是a[2][2]


指针

指针变量

与普通变量不同,指针定义时的数据类型并不是指针变量本身的数据类型,而是其所指目标数据的数据类型,称之为基类型。指针初始化的一般形式如下:

数据类型  * 指针名=初始地址值;

int *p;
int a=10;
p=&a;
printf("%d\n",*p);

在定义指针变量后系统为该指针变量分配一个地址大小的存储单元,指针变量中存放的是地址值。特别注意,无论指针变量的基类型是何种数据类型,占用的内存大小都是相同的,例如“char *p1;double *p2”,在32位系统中有sizeof(p1)=sizeof(p2)=4,即p1和p2两个指针变量均占用4个字节。

尽管任何指针变量中存放的都是地址值,但一个指针变量只能存放相同基类型的数据地址

一个指针变量本身占用的空间很小,但可以指向一整块很大的连续控件,这个一整块的连续控件通常是采用动态分配函数malloc分配,存放在堆空间中。

指针和地址有什么区别?指针意味着已经有一个指针变量存在,其值是一个地址,指针变量本身也有地址,而地址本身并不代表任何变量存在,地址仅表示内存空间的一个位置;

指针运算
指针与整数的加减:p±n 以该地址量为基点的其阿芳或后方的第n个数据的地址。例如,对于基类型为type的指针p,p±n的结果是地址值p±n*sizeof(type),其中sizeof(type)称之为步长,显然步长是与指针变量的基类型有关
指针++和–:++代表运算后指向下一个数据的位置,指针–代表运算后指向上一个数据的位置,运算后指针地址值的变化量取决于滋镇的基类型;

两个指针相减:并非他们的两个地址值直接做减法运算,而是两指针所指地址之间的数据个数。
NULL(与0等效):一旦某个指针赋值为NULL,就将该指针值设置为0,表示指针变量没有意义,就不能再存储数据,如int *p=NULL, *p=10,是错误的;

也就是说我们一定要记住,指针运算指的是针对指针的基类型进行的运算,而不是地址的加加减减;

面试题

以下程序的输出是()

int *pint=0;
pint+=6;
printf("%d\n",pint);

答案:24
首先题中在定义指针的时候p=0,也就是相当于p=NULL.
其次,+6,其实也就是相当于+6*sizeof(pint);所以输出的地址值是24
以下程序的输出结果是什么?

#include <stdio.h>
void main()
{
	int*p1,*p2;
	int value;
	p1=(int *)0x500;
	p2=(int *)0x508;
	value=p2-p1;
	printf("%d\n",value);
}

输出结果是2
解析:8/4=2

以下程序执行会输出什么?

#include<stdio.h>
int main()
{
	char *p="Linux";//将字符串的首地址赋给p
	printf("%d\n",p);
	printf("[%c]",*p++);
	printf("[%c]",*p);
	return 0;


}

常量指针变量

在C++中还可以用const修饰指针变量名,称之为常量指针变量;
char *const p;
表示指针p是一个常量指针变量,p的值不能再法神改变,所以必须初始化,一旦初始化,p不能指向其他数据,但可以通过指针p修改所指的内容;

 #include<stdio.h>
#include <malloc.h>
#include <string.h>
int main()
{
	int a=10;
int *p=&a;
int * const q=p;
*q=56;

printf("shuchu %d\n",*p);
printf("chuchu %d\n",*q);
}

指针常量

指针变量即为常量指针,又是常量指针变量,例如:

const char  * const p;

指针常量,前一个const修饰指针变量的定义,后一个const修饰变量名,指针p必须初始化,并且p的值和指向的内容都不能修改;

#include<stdio.h>
#include <malloc.h>
#include <string.h>
int main()
{
	int a=10;
int *p=&a;
const int * const q=p;//指针常量
*q=56;

printf("shuchu %d",*p);
printf("chuchu %d",*q);
}

编译错误

常量指针

在定义指针时用const关键字进行修饰,称为const指针常量;

const char *p

总结
 

const int *p=&a;
int const *p=&a;

以上这两个表达式是相同的,都是常量指针,两个const 都是针对*p,所以值不能改变,而指向地址可以改变;而

int * const p ;

const是去修饰 p指针的,所以指针地址不能改变,但是指向的内容值是可以改变的,故称之为常量指针变量;最后一种

const int * const p

是同时针对地址和值,所以都不能改变;

void * 的含义与void **

void *p1;
int *p2;
p1=p2;

"无类型”可以包含“有类型”,void *可以指向任何类型的数据

void ** 可以看做 (void * *),也就是类型void * 的类型转换;
(void *) 相当于一个转换成一个无类型的地址;

int n=10;
int *p=& n;
printf("%d\n",*(void ** )p); 

(void **)p 相当于(int *) (void *p)
也就是说,( void ** )p相当于转换成空指针类型,再强制类型转成p的基类型;

面试题1

int main()
{
	char * p="hello ,world";
	return 0;
}

指针变量是在栈控件分配的,它指向的常量字符串“hello,world”是在静态数据区分配的;

面试题2

#include<stdio.h>
#include <malloc.h>
#include <string.h>
void main()
{
	int i,*p;
	p=(int *)malloc(3*sizeof(int));//分配若干个字节的空间,并返回这些字节的首地址
	for(i=0;i<3;i++)
	{
		*(p+i)=i+1;
		printf("%d\n",*(p+i));
	}
p++;
	free(p);//free的参数必须是某块内存的首地址
}

由于释放程序的时候,free释放的不是分配时候的起始地址,所以会导致程序崩溃。把p++注释掉即可;

#include <stdio.h>
#include <malloc.h>
void main()
{
	int i,*p,**pp=&p;
	p=(int *)malloc(3*sizeof(int));
	for(i=0;i<3;i++)
			*(p+i)=i+1;
	printf("%d\n",**(++pp));
	free(p);

}

对于表达式**(++pp),先执行++pp,是增加pp的地址值,此时pp的地址值加1,会指向一个垃圾地址;

斐波拉契数列实现

迭代实现:

#include <stdio.h>

int main(void)
{
    int n;
    int n1 = 1, n2 = 1;
    int n3;
    scanf("please input num: %d" &n);
    //迭代实现
    if(n<=2)
        printf("数列: %d %d\n", n1, n2);
    else
    {
        printf("数列: %d %d", n1, n2);
        for(int i = 3; i <= n; i++)
        {
            n3 = n1 + n2;
            printf(" %d", n3);
            n1 = n2;
            n2 = n3;
        }
        printf("\n");
    }
}

递归实现:

int Fib(int n)
{ // 自定义函数
	if(n<0)
	      return -1;
	else if(n==0)
	      return 0;
	else if(n==1)
	      return 1;
    else
          return Fib(n-1)+Fib(n-2);
}

int main(){
	int num;
	printf("请输入要求取的第n项斐波那契数列 n="); 
	scanf("%d", &num);
	for(int i = 1; i <= num; i++)
	{
		printf(" %d",Fib(i));	
	}
	printf("\n");
	return 0;
}

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值