写在前面,这是前几去面试一份笔试题,自己做的简直太糟糕了,暴露出了自己的基础知识实在是太不扎实了,自己面试也没有什么准备,所以就将题带回来了,现在完整的做一遍,警醒自己也作复习之用。
1、程序的局部变量存在于(堆栈)中,全局变量存在于(数据段)中,动态申请数据存在于(堆空间)中
注意:初始化过的全局变量存在于数据段中,未初始化的存在于bss段。
2、下列语句中p的含义
int *p[n];
首先p与[]结合,因为其优先级比*高,说明p是一个数组,再与*结合,说明数组里面的元素是指针类型,再与int结合,说明指针指向的内容的类型是int型,所以p是一个指向整型数据的指针组成的数组
int (*p)[n]
首先p与*结合,说明p是一个指针,然后与[]结合,说明指针指向的是一个数组,然后再与int结合,说明数组里的内容的类型是int型,所以p是一个指向整型数组的指针
int *p()
首先p与()结合,说明p是一个函数,再与*结合,说明函数的返回值是一个指针,再与int结合,说明指针指向的内容的类型是整型,所以p是一个返回值为一个指向整型数据的指针的函数
int (*p)()
首先p与*结合,说明p是一个指针,再与()结合,说明指针指向的是函数,再与int结合,说明函数的返回值是整型,所以p是一个指向返回值为整型的指针。
int p; //这是一个普通的整型变量
int *p; //首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是一个返回整型数据的指针
int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组
int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组
int (*p)[3]; //首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针
int **p; //首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针.
int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据
Int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针
int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.
3、写一个"标准"宏MIN,这个宏输入两个参数并返回较小的一个
#define MIN(x,y) ((x)<(y)?(x):(y))
4、关键字static的作用是什么?
- 第一,为某特定数据类型或对象分配单独的存储空间,而与创建对象的个数无关。
- 第二,希望某个方法或属性与类而不是对象关联在一起,也就是说,在不创建对象的情况下就可以通过类来直接调用方法或使用类的属性。
-
具体而言,static在Java语言中主要有四种使用情况:成员变量、成员方法、代码块及内部类。以下将分别对这4种情况进行介绍
(1)static成员变量(类变量)
虽然Java语言中没有全局的概念,但可以通过static 关键字来达到全局的效果。Java类态变量属于类,提供了两种类型的变量。用static关键字修饰的静态变量和没有static关键字的实例变量在内存中只有一个副本(所有实例都指向同一一个内存地址),只要静态变量所在的类被加载, 这个静态变量就会被分配空间,因此,就可以被使用了。对静态变量的引用有两种方式,分别为“类静态变量”和“对象静态变量”。
实例变量属于对象,只有对象被创建后,实例变量才会被分配空间,才能被使用,它在内存中存在多个副本。只能用“对象.静态变量”的方式来引用。(2) static 成员方法
与变量类似,Java类同时也提供了static方法与非static方法。static 方法是类的方法,不需要创建对象就可以被调用,而非static方法是对象的方法,只有对象被创建出来后才可以被使用。
static方法中不能使用this和super关键字,不能调用非static方法,只能访问所属类的静态成员变量和成员方法,因为当static方法被调用时,这个类的对象可能还没被创建,即使已经被创建了,也无法确定调用哪个对象的方法。同理,static 方法也不能访问非static类型的变量。实现了单例。(3).Static代码块
Static代码块又叫静态代码块,在类中独立于成员变量和成员函数的代码块。它不在任何一个方法体内,JVM在加载类的时候执行静态代码块,多个存在则顺序执行,常用来初始化静态变量。而且只被执行一次,如果是在主类中,会优先于main方法执行。
(4).Static内部类
被static修饰的类相当于外部类,但是内部类不能与外部类的类名相同,只有内部类才能被定义为static(可以理解为内部类也是类的一个成员)。
5、以下三条语句分别输出什么?
char str1[]="abc"
char str2[]="abc"
const char str3[]="abc"
const char str4[]="abc"
const char* str5[]="abc"
const char* str6[]="abc"
cout<<boolalpha<<(str1==str2)<<endl;
cout<<boolalpha<<(str3==str4)<<endl;
cout<<boolalpha<<(str5==str6)<<endl;
答案是 false false true
情况一:两者不相等,是因为str1 和 str2 都是字符数组,每个都有其自己的存储区,它们的值则是各存储区的首地址
情况二:两者不相等,str3和str4两个字符数组都存储在栈空间上,但两者地址值不相等。
情况三:两者相等,str5 和str6并非字符数组而是字符指针,并不分配存储区,其后的“abc”以常量形式存于常量区,str5 和 str6 是指它们指向的地址的首地址,而它们自己仅是指向该区首地址的指针,所以相等(&str5 和 &str6 是指指针自己的地址,所以两者地址是不相等的)。
6、写出以下代码的打印结果
#include<stdio.h>
void main(){
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
return;
}
输出结果是2,5
&a为取数组名的地址,即为整个数组的地址,它的类型为数组类型,(int*)(&a+1)将其强制成整型指针赋值给*ptr,并指向了数组界外,最后用*(ptr-1)又指向了界内的最后一个元素5。
7、下列代码的输出是什么
void foo(void){
unsigned int a=6;
int b=-20;
(a+b>6) puts(">6"):puts("<=6");
}
此题输出为>6
计算机中的符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同。
反码表示法规定:正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外。
补码表示法规定:正数的补码与其原码相同;负数的补码是在其反码的末位加1。
int类型的最高位表示正负,如果最高位是1,则表示负数。而unsigned int的最高位是有效数位。
当int和unsigned in相加时,要将int转化为unsigned int,而int小于0,所以它的最高位是符号位,为1,所以转化的结果是一个很大的正数,在第一个if语句中,是两个“正数”相加,结果自然就大于0了。
int a = -6;
unsigned int b = 4;
int z = a+b;
if(z > 0)
printf("z>0\n");
else
printf("z<0\n");//这句话被打印
unsigned int uni=a+b;
int i = a+b;
printf("uni=%u, i=%d\n", uni, i);//uni=4294967294, i=-2
printf("uni=%d, i=%d\n", uni, i);//uni=-2, i=-2
printf("uni=%u, i=%u\n", uni, i);//uni=4294967294, i=4294967294
if(i==uni){
printf("equal\n");//这句话被打印
}else{
printf("not equal\n");
}
z = a+b这一句时,它把a+b的结果看做一个int类型,而a+b最高位为1,所以z是一个负数,所以打印的是第二个语句。
8、下面的代码是否有问题
void test1(){
char string[10];
char *str1="0123456789";
strcpy(string,str1);
}
有问题,str1指向的字符串中有0-9共10个字符,加上用来标记字符串结束的在'9'之后的末尾的0字节,共计要占用11个字节,而string的大小为10,所以会报错。
9、下面的代码是否有问题,如果存在问题,需要如何修改
char *GetMemory(void){
char p[]="hello world";
return p;
}
void Test(void){
char *str=NULL;
str = GetMemory();
printf(str);
}
有问题,p的生命周期在GetMemory函数执行完了就被销毁了,str 指向的是个野指针,输出的是乱码。
10、分别输出A、B、C三个结构体的大小
struct A{
int a;
char b;
short c;
};
struct B{
char b;
int a;
short c;
};
#pragma pack(1)
struct C{
char b;
int a;
};
三个结构体的大小分别为8、12、5
b是char型数据,占用1字节内存;short型数据,占用2字节内存;int型数据,占用4字节内存。因此,结构体A的自身对齐值为4。于是,a和b要组成4个字节,以便与c的4个字节对齐。而a只有1个字节,a与b之间便空了一个字节。
第一种情况(网上找的图,变量对应有问题,但是意思是一样的)
第二种情况
四个概念:
1)数据类型自身的对齐值:就是基本数据类型的自身对齐值,比如char类型的自身对齐值为1字节,int类型的自身对齐值为4字节。
2)指定对齐值:预编译命令#pragma pack (value)指定的对齐值value。
3)结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值,比如以上的struct A的对齐值为4。
4)数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小的那个值。
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节{trailing padding}。
对于以上规则的说明如下:
第一条:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。
第二条:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员大小的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。
第三条:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。
15、下面程序的执行后的错误或者效果是
#define MAX 255
int main(){
unsigned char A[MAX],i;//i被定义为unsigned char
for(int i=0;i<=MAX;i++)
A[i]=i;
}
死循环加数组越界访问(C/C++不进行数组越界检查) ,MAX=255 数组A的下标范围为:0..MAX-1,这是其一..
其二.当i循环到255时,循环内执行: A[255]=255; 这句本身没有问题..但是返回for (i=0;i<=MAX;i++)语句时, 由于unsigned char的取值范围在(0..255),i++以后i又为0了..无限循环下去.
18、编程题,已知两个链表head1和head2各自有序,请把他们合并成一个链表依然有序(保留所有节点,即便大小相同)
Node * Merge(Node *head1 , Node *head2)
{
if ( head1 == NULL)
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
Node *p1 = NULL;
Node *p2 = NULL;
if ( head1->data < head2->data )
{
head = head1 ;
p1 = head1->next;
p2 = head2 ;
}
else
{
head = head2 ;
p2 = head2->next ;
p1 = head1 ;
}
Node *pcurrent = head ;
while ( p1 != NULL && p2 != NULL)
{
if ( p1->data <= p2->data )
{
pcurrent->next = p1 ;
pcurrent = p1 ;
p1 = p1->next ;
}
else
{
pcurrent->next = p2 ;
pcurrent = p2 ;
p2 = p2->next ;
}
}
if ( p1 != NULL )
pcurrent->next = p1 ;
if ( p2 != NULL )
pcurrent->next = p2 ;
return head ;
}