面试题3
2016-06-16 17:38
716 查看
1: short i = 0; i = i + 1;这两句有错吗?
2:typedef和define有什么区别
(1)用法不同:typedef用来定义一种数据类型的别名,增强程序的可读性。define主要用来定义常量,以及书写复杂使用频繁的宏。
(2)执行时间不同:typedef是编译过程的一部分,有类型检查的功能。define是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
(3)作用域不同:typedef有作用域限定。define不受作用域约束,只要是在define声明后的引用都是正确的。
(4)对指针的操作不同:typedef和define定义的指针时有很大的区别。
注意:typedef定义是语句,因为句尾要加上分号。而define不是语句,千万不能在句尾加分号。
3:流操作符重载为什么返回引用
在程序中,流操作符>>和<<经常连续使用。因此这两个操作符的返回值应该是一个仍旧支持这两个操作符的流引用。其他的数据类型都无法做到这一点。
注意:除了在赋值操作符和流操作符之外的其他的一些操作符中,如+、-、*、/等却千万不能返回引用。因为这四个操作符的对象都是右值,因此,它们必须构造一个对象作为返回值。
4:中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展——让标准C支持中断。具体代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt关键字去定义一个中断服务子程序(ISR),请评论以下这段代码。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf(" Area = %f", area);
return area;}
【答案】
这段中断服务程序主要有以下四个问题:
关键字__interrupt定义的中断处理函数,不能有返回值和函数参数
(1)ISR 不能返回一个值,必须用void。
(2)ISR 不能传递参数,必须用void。
(3)ISR应该是短而高效的,在ISR 中做浮点运算是不明智的。
(4)printf()经常有重入和性能上的问题。
5:介绍一下STL,详细说明STL如何实现vector。
STL (标准模版库,Standard Template Library)它由容器算法迭代器组成。 从根本上说,STL是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其他一些组件的集合。
STL有以下的一些优点:
可以方便容易地实现搜索数据或对数据排序等一系列的算法;
调试程序时更加安全和方便;
即使是人们用STL在UNIX平台下写的代码你也可以很容易地理解(因为STL是跨平台的)。
vector实质上就是一个动态数组,会根据数据的增加,动态的增加数组空间。
6:指针和引用有什么分别;如果传引用比传指针安全,为什么?如果我使用常量指针难道不行吗?
(1) 引用在创建的同时必须初始化,即引用到一个有效的对象;而指针在定义的时候不必初始化,可以在定义后面的任何地方重新赋值.
(2) 不存在NULL引用,引用必须与合法的存储单元关联;而指针则可以是NULL.
(3) 引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用;而指针在任何时候都可以改变为指向另一个对象.给引用赋值并不是改变它和原始对象的绑定关系.
(4) 引用的创建和销毁并不会调用类的拷贝构造函数
(5) 语言层面,引用的用法和对象一样;在二进制层面,引用一般都是通过指针来实现的,只不过编译器帮我们完成了转换.
不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,显得很安全。
const 指针仍然存在空指针,并且有可能产生野指针.
总的来说:引用既具有指针的效率,又具有变量使用的方便性和直观性.
7:C++特点是什么,如何实现多态?画出基类和子类在内存中的相互关系。
多态的基础是继承,需要虚函数的支持,简单的多态是很简单的。
子类继承父类大部分的资源,不能继承的有构造函数,析构函数,拷贝构造函数,oper ator=函数,友元函数等等
8:你如何理解MVC。简单举例来说明其应用。
MVC模式是observer 模式的一个特例,典型的有MFC里面的文档视图架构。
MVC只是一种设计模式,不算是框架,一共分为三层:
m层(模型)里放的是与数据库连接以及其他具体操作的php文件;
v层(视图)里放的是前台页面html,:
c层(控制器)里主要放的是控制前台页面与后台连接数据库文件的相关操作的php文件;
MVC模式由一个入口文件进入到控制器里,然后再由控制器来分配任务跳转到前台页面或是将前台页面提交过来的值交给M层来进行与数据库的操作.
9:
下面通过一个例子来解析 常量指针 和 指针常量,我们先总结一下 常量指针 和 指针常量 的区别
首先一定要明白哪种定义方式是常量指针,哪种是指针常量,这里可以记住三句话加深记忆:
* (指针)和 const(常量) 谁在前先读谁 ;*象征着地址,const象征着内容;谁在前面谁就不允许改变。
好吧,让我们来看这个例子:
[cpp] view
plain copy
int a =3;
int b = 1;
int c = 2;
int const *p1 = &b;//const 在前,定义为常量指针
int *const p2 = &c;//*在前,定义为指针常量
![](file:///C:/Users/HP7100/AppData/Local/youdao/ynote/images/998106F096954149BA6114414C13BC79/857227d4adda49c190b61622aba0d8b9.jpg)
常量指针p1:指向的地址可以变,但内容不可以重新赋值,内容的改变只能通过修改地址指向后变换。
p1 = &a是正确的,但 *p1 = a是错误的。
指针常量p2:指向的地址不可以重新赋值,但内容可以改变,必须初始化,地址跟随一生。
p2= &a是错误的,而*p2 = a 是正确的。
10:
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
char *str7 = "abc";
char *str8 = "abc";
cout << ( str1 == str2 ) << endl;
cout << ( str3 == str4 ) << endl;
cout << ( str5 == str6 ) << endl;
cout << ( str7 == str8 ) << endl;
结果是:0 0 1 1
解答:str1,str2,str3,str4是数组变量,它们有各自的内存空间;而str5,str6,str7,str8是指针,它们指向相同的常量区域。
11:要对绝对地址0x100000赋值,我们可以用(unsigned int*)0x100000 = 1234;那么要是想让程序跳转到绝对地址是0x100000去执行,应该怎么做?
*((void (*)( ))0x100000 ) ( );
首先要将0x100000强制转换成函数指针,即: (void (*)())0x100000
然后再调用它: *((void (*)())0x100000)();
用typedef可以看得更直观些:
typedef void(*)() voidFuncPtr;
*((voidFuncPtr)0x100000)();
12: 已知一个数组table,用一个宏定义,求出数据的元素个数
#define NTBL
#define NTBL (sizeof(table)/sizeof(table[0]))
13:#include "stdafx.h"
#include "string.h"
void main()
{
char aa[10];
printf("%d\n",strlen(aa)); //23, strlen()和初始化有关
printf("%d",sizeof(aa));//10,sizeof()和初不初始化,没有关系
getchar();
getchar();
}
//strlen用于计算字符串长度,直到遇到结束符'\0'停止,声明一个字符数组aa[10],不进行初始化,数组中每一项是一个随机值,编译器会在数组末尾往后第n个地址的值初始化为0,n的值看编译器而定
//strlen()在求一个字符串的长度时, 是以'\0'(字符串结尾符)为结尾的, 也就是说只有遇到'\0'后strlen()才会停止计数, 你的数组aa没有初始化, 其中的内容都是不确定的, 这取决于你内存中数据的具体情况, 也许到了15的时候遇到'\0'结尾符, 所以长度记为15,但这都是没有意义的, 即使结果是10, 也只能算是凑巧了,所以再用strlen()求字符串的长度时一定要确保有'\0'结尾符.
14:
-16 1
简单说结果怎么来的,把str变量cc的存储信息(二进制码)拷贝到结构变量aa的内存中(5bit的b1和2bit的
b2),0的二进制ascii码是00110000(48,用win自带计算器自己转换一下),b1得后五位10000,b2得接下来的两位01(倒着
取,顺着排),b1是符号int,所以是-16,b2是无符号int,所以是1
顺便说一句,0,a这些打头的acsII编号,最好记牢,其他顺着数就行了,
15:求函数返回值,输入x=9999;
int func ( x )
{
int countx = 0;
while ( x )
{
countx ++;
x = x&(x-1);
}
return countx;
}
结果呢? 知道了这是统计9999的二进制数值(10011100001111)中有多少个1的函数,用这种方法来求1的个数是很效率很高的。
不必去一个一个地移位。循环次数最少。
16:int a,b,c 请写函数实现C=a+b ,不可以改变数据类型,如将c改为long int,关键是如何处理溢出问题
bool add (int a, int b,int *c)
{
*c=a+b;
return (a>0 && b>0 &&(*c<a || *c<b) || (a<0 && b<0 &&(*c>a || *c>b)));
}
x86架构的CPU处理加减法的溢出是这样的:
例如char类型表示的数据范围是-128 — 127,那么127+2=-127,-127-2=127;
两个int数相加,只有两个数的符号相同的时候才会溢出:
1,两个正数相加: a>0 && b>0 就是判断两数都是正数;如果没有溢出,则和必定大于a、b,反之溢出;
2,两负数相加: a<0 && b<0 判断两数都是负数;如果没有溢出,则和必定小于a、b,反之溢出;
综合起来就是:return (a>0 && b>0 && (*c<a||*c<b) || (a<0 && b<0 && (*c>a || *c>b)));
17:struct
bit
{
int a:3;
int b:2;
int
c:3;
};
int main()
{
bit s;
char *c=(char*)&s;
cout<<sizeof(bit)<<endl;
*c=0x99;
cout << s.a <<endl <<s.b<<endl<<s.c<<endl;
int a=-1;
printf("%x",a);
return 0;
}
输出为什么是
4
1
-1
-4
ffffffff
因为0x99在内存中表示为 100 11 001 , a = 001, b = 11, c = 100
当c为有符合数时, c = 100, 最高1为表示c为负数,负数在计算机用补码表示,所以c = -4;同理
b = -1;
当c为有符合数时, c = 100,即 c = 4,同理 b = 3
原码:将一个整数,转换成二进制,就是其原码。如单字节的5的原码为:0000 0101;-5的原码为1000 0101。
反码:正数的反码就是其原码;负数的反码是将原码中,除符号位以外,每一位取反。如单字节的5的反码为:0000 0101;-5的原码为1111 1010。
补码:正数的补码就是其原码;负数的反码+1就是补码。如单字节的5的补码为:0000 0101;-5的补码为1111 1011。
在计算机中,正数是直接用原码表示的,如单字节5,在计算机中就表示为:0000 0101。负数用补码表示,如单字节-5,在计算机中表示为1111 1011。
18:
改错:
#include <stdio.h>
int main(void)
{
int **p;
int arr[100];
p = &arr;
return 0;
}
解答:搞错了,是指针类型不同,
int **p; //二级指针
&arr; //得到的是指向第一维为100的数组的指针
#include <stdio.h>
int main(void)
{
int **p, *q;
int arr[100];
q = arr;
p = &q;
return 0;
}
19:下面这个程序执行后会有什么错误或者效果:
#define MAX 255
int main()
{
unsigned char A[MAX],i;//i被定义为unsigned char
for (i=0;i<=MAX;i++)
A[i]=i;
return 0;
} 解答:死循环加数组越界访问(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了..无限循环下去。
20:struct name1
{
char str;
short x;
int num;
}
struct name2
{
char str;
int num;
short x;
}
sizeof(struct name1)=8,sizeof(struct name2)=12
在第二个结构中,为保证num按四个字节对齐,char后必须留出3字节的空间;同时为保证整个结构的自然对齐(这里是4字节对齐),在x后还要补齐2个字节,这样就是12字节。
21:A.c 和B.c两个c文件中使用了两个相同名字的static变量,编译的时候会不会有问题?这两个static变量会保存到哪里(栈还是堆或者其他的)?
static的全局变量,表明这个变量仅在本模块中有意义,不会影响其他模块。他们都放在数据区,但是编译器对他们的命名是不同的。如果要使变量在其他模块也有意义的话,需要使用extern关键字。
22:
2013-10-12
12:13
提问者采纳
23:堆与栈的去区别
A. 申请方式不同
Stack由系统自动分配,而heap需要程序员自己申请,并指明大小。
B. 申请后系统的响应不同
stack:只要栈的剩余空间大于申请空间,系统就为程序提供内存,否则将抛出栈溢出异常
Heap:当系统收到程序申请时,先遍历操作系统中记录空闲内存地址的链表,寻找第一个大于所申请空间的堆结点,然后将该结点从空间结点链表中删 除,并将该结点的空间分配给程序。另外,大多数系统还会在这块内存空间中的首地址处记录本次分配的大小,以便于delete语句正确释放空间。而且,由于
找到的堆结点的大小不一定正好等于申请的大小,系统会自动将多余的那部分重新放入空闲链表。
C. 申请大小限制的不同
Stack:在windows下,栈的大小是2M(也可能是1M它是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
Heap:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
D. 申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。
E. 堆和栈中的存储内容
栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器 中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开
始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
24:含参数的宏与函数的优缺点
宏:
优点:在预处理阶段完成,不占用编译时间,同时,省去了函数调用的开销,运行效率高
缺点:不进行类型检查,多次宏替换会导致代码体积变大,而且由于宏本质上是字符串替换,它的定义很容易产生二义性,故可能会由于一些参数的副作用导致得出错误的结果。
函数:
优点:没有带参数宏可能导致的副作用,进行类型检查,计算的正确性更有保证。
缺点:函数调用需要参数、返回地址等的入栈、出栈开销,效率没有带参数宏高
PS:宏与内联函数的区别: 内联函数和宏都是在程序出现的地方展开,内联函数不是通过函数调用实现的,是在调用该函数的程序处将它展开(在编译期间完成的);不同的是:内联函数可以在编译期间完成诸如类型检测,语句是否正确等编译功能;宏就不具有这样的功能,而且宏展开的时间和内联函数也是不同的(在预编译期间展开)
25:7Windows程序的入口是哪里?写出Windows消息机制的流程
Windows程序的入口是WinMain()函数。
Windows应用程序消息处理机制:
A. 操作系统接收应用程序的窗口消息,将消息投递到该应用程序的消息队列中
B. 应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息,取出消息后,应用程序可以对消息进行一些预处理。
C. 应用程序调用DispatchMessage,将消息回传给操作系统。
D. 系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理。
26:请写出下列代码的输出内容
#include<stdio.h>
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
27、请找出下面代码中的所以错误 说明:以下代码是把一个字符串倒序,如“abcd”倒序后变为“dcba”
#include "string.h"
main()
{
char*src="hello,world";
char* dest=NULL;
int len=strlen(src);
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 <stdio.h>
#include <string.h>
main() {
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;
}
28:-1,2,7,28,,126请问28和126中间那个数是什么?为什么?
答案应该是4^3-1=63
规律是n^3-1(当n为偶数0,2,4) n^3+1(当n为奇数1,3,5)
答案:63 9
29:在c语言库函数中将一个字符转换成整型的函数是atool()吗,这个函数的原型是什么?
函数名: atol
功 能: 把字符串转换成长整型数
用 法: long atol(const char *nptr);
程序例:
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
long l;
char *str = "98765432";
l = atol(lstr);
printf("string = %s integer = %ld\n", str, l);
return(0);
}
30:
unsigned char *p1;
unsigned long *p2;
p1=(unsigned char *)0x801000;
p2=(unsigned long *)0x810000;
请问p1+5= ?0x801005; p2+5= ?0x801014;
p1+5=0x801005; p2+5=0x801014;
short类型长度比int类性短,当short + int 时候默认结果是类型较长的一方也就是int, 所以 i + 1 值是int类型,不能赋值给short类型,也就是说长的类型不能赋值给短的类型,除非强制转型(但这样会产生精度问题) 还有,short i = 0;i += 1 这样写是对的,单目运算符+=,-= ...强制转换为长度短的类型。
2:typedef和define有什么区别
(1)用法不同:typedef用来定义一种数据类型的别名,增强程序的可读性。define主要用来定义常量,以及书写复杂使用频繁的宏。
(2)执行时间不同:typedef是编译过程的一部分,有类型检查的功能。define是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
(3)作用域不同:typedef有作用域限定。define不受作用域约束,只要是在define声明后的引用都是正确的。
(4)对指针的操作不同:typedef和define定义的指针时有很大的区别。
注意:typedef定义是语句,因为句尾要加上分号。而define不是语句,千万不能在句尾加分号。
3:流操作符重载为什么返回引用
在程序中,流操作符>>和<<经常连续使用。因此这两个操作符的返回值应该是一个仍旧支持这两个操作符的流引用。其他的数据类型都无法做到这一点。
注意:除了在赋值操作符和流操作符之外的其他的一些操作符中,如+、-、*、/等却千万不能返回引用。因为这四个操作符的对象都是右值,因此,它们必须构造一个对象作为返回值。
4:中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展——让标准C支持中断。具体代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt关键字去定义一个中断服务子程序(ISR),请评论以下这段代码。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf(" Area = %f", area);
return area;}
【答案】
这段中断服务程序主要有以下四个问题:
关键字__interrupt定义的中断处理函数,不能有返回值和函数参数
(1)ISR 不能返回一个值,必须用void。
(2)ISR 不能传递参数,必须用void。
(3)ISR应该是短而高效的,在ISR 中做浮点运算是不明智的。
(4)printf()经常有重入和性能上的问题。
5:介绍一下STL,详细说明STL如何实现vector。
STL (标准模版库,Standard Template Library)它由容器算法迭代器组成。 从根本上说,STL是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其他一些组件的集合。
STL有以下的一些优点:
可以方便容易地实现搜索数据或对数据排序等一系列的算法;
调试程序时更加安全和方便;
即使是人们用STL在UNIX平台下写的代码你也可以很容易地理解(因为STL是跨平台的)。
vector实质上就是一个动态数组,会根据数据的增加,动态的增加数组空间。
6:指针和引用有什么分别;如果传引用比传指针安全,为什么?如果我使用常量指针难道不行吗?
(1) 引用在创建的同时必须初始化,即引用到一个有效的对象;而指针在定义的时候不必初始化,可以在定义后面的任何地方重新赋值.
(2) 不存在NULL引用,引用必须与合法的存储单元关联;而指针则可以是NULL.
(3) 引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用;而指针在任何时候都可以改变为指向另一个对象.给引用赋值并不是改变它和原始对象的绑定关系.
(4) 引用的创建和销毁并不会调用类的拷贝构造函数
(5) 语言层面,引用的用法和对象一样;在二进制层面,引用一般都是通过指针来实现的,只不过编译器帮我们完成了转换.
不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,显得很安全。
const 指针仍然存在空指针,并且有可能产生野指针.
总的来说:引用既具有指针的效率,又具有变量使用的方便性和直观性.
7:C++特点是什么,如何实现多态?画出基类和子类在内存中的相互关系。
多态的基础是继承,需要虚函数的支持,简单的多态是很简单的。
子类继承父类大部分的资源,不能继承的有构造函数,析构函数,拷贝构造函数,oper ator=函数,友元函数等等
8:你如何理解MVC。简单举例来说明其应用。
MVC模式是observer 模式的一个特例,典型的有MFC里面的文档视图架构。
MVC只是一种设计模式,不算是框架,一共分为三层:
m层(模型)里放的是与数据库连接以及其他具体操作的php文件;
v层(视图)里放的是前台页面html,:
c层(控制器)里主要放的是控制前台页面与后台连接数据库文件的相关操作的php文件;
MVC模式由一个入口文件进入到控制器里,然后再由控制器来分配任务跳转到前台页面或是将前台页面提交过来的值交给M层来进行与数据库的操作.
9:
下面通过一个例子来解析 常量指针 和 指针常量,我们先总结一下 常量指针 和 指针常量 的区别
首先一定要明白哪种定义方式是常量指针,哪种是指针常量,这里可以记住三句话加深记忆:
* (指针)和 const(常量) 谁在前先读谁 ;*象征着地址,const象征着内容;谁在前面谁就不允许改变。
好吧,让我们来看这个例子:
[cpp] view
plain copy
int a =3;
int b = 1;
int c = 2;
int const *p1 = &b;//const 在前,定义为常量指针
int *const p2 = &c;//*在前,定义为指针常量
![](file:///C:/Users/HP7100/AppData/Local/youdao/ynote/images/998106F096954149BA6114414C13BC79/857227d4adda49c190b61622aba0d8b9.jpg)
常量指针p1:指向的地址可以变,但内容不可以重新赋值,内容的改变只能通过修改地址指向后变换。
p1 = &a是正确的,但 *p1 = a是错误的。
指针常量p2:指向的地址不可以重新赋值,但内容可以改变,必须初始化,地址跟随一生。
p2= &a是错误的,而*p2 = a 是正确的。
10:
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
char *str7 = "abc";
char *str8 = "abc";
cout << ( str1 == str2 ) << endl;
cout << ( str3 == str4 ) << endl;
cout << ( str5 == str6 ) << endl;
cout << ( str7 == str8 ) << endl;
结果是:0 0 1 1
解答:str1,str2,str3,str4是数组变量,它们有各自的内存空间;而str5,str6,str7,str8是指针,它们指向相同的常量区域。
11:要对绝对地址0x100000赋值,我们可以用(unsigned int*)0x100000 = 1234;那么要是想让程序跳转到绝对地址是0x100000去执行,应该怎么做?
*((void (*)( ))0x100000 ) ( );
首先要将0x100000强制转换成函数指针,即: (void (*)())0x100000
然后再调用它: *((void (*)())0x100000)();
用typedef可以看得更直观些:
typedef void(*)() voidFuncPtr;
*((voidFuncPtr)0x100000)();
12: 已知一个数组table,用一个宏定义,求出数据的元素个数
#define NTBL
#define NTBL (sizeof(table)/sizeof(table[0]))
13:#include "stdafx.h"
#include "string.h"
void main()
{
char aa[10];
printf("%d\n",strlen(aa)); //23, strlen()和初始化有关
printf("%d",sizeof(aa));//10,sizeof()和初不初始化,没有关系
getchar();
getchar();
}
//strlen用于计算字符串长度,直到遇到结束符'\0'停止,声明一个字符数组aa[10],不进行初始化,数组中每一项是一个随机值,编译器会在数组末尾往后第n个地址的值初始化为0,n的值看编译器而定
//strlen()在求一个字符串的长度时, 是以'\0'(字符串结尾符)为结尾的, 也就是说只有遇到'\0'后strlen()才会停止计数, 你的数组aa没有初始化, 其中的内容都是不确定的, 这取决于你内存中数据的具体情况, 也许到了15的时候遇到'\0'结尾符, 所以长度记为15,但这都是没有意义的, 即使结果是10, 也只能算是凑巧了,所以再用strlen()求字符串的长度时一定要确保有'\0'结尾符.
14:
#include<iostream> using namespace std; #include<string.h> typedef struct AA { int b1:5; int b2:2; }AA; void main() { AA aa; cout<<sizeof(AA)<<endl; char cc[100]; cout<<strlen(cc)<<endl; strcpy(cc,"0123456789abcdefghijklmnopqrstuvwxyz"); memcpy(&aa,cc,sizeof(AA)); cout << aa.b1 <<endl; cout << aa.b2 <<endl; }
-16 1
简单说结果怎么来的,把str变量cc的存储信息(二进制码)拷贝到结构变量aa的内存中(5bit的b1和2bit的
b2),0的二进制ascii码是00110000(48,用win自带计算器自己转换一下),b1得后五位10000,b2得接下来的两位01(倒着
取,顺着排),b1是符号int,所以是-16,b2是无符号int,所以是1
顺便说一句,0,a这些打头的acsII编号,最好记牢,其他顺着数就行了,
15:求函数返回值,输入x=9999;
int func ( x )
{
int countx = 0;
while ( x )
{
countx ++;
x = x&(x-1);
}
return countx;
}
结果呢? 知道了这是统计9999的二进制数值(10011100001111)中有多少个1的函数,用这种方法来求1的个数是很效率很高的。
不必去一个一个地移位。循环次数最少。
16:int a,b,c 请写函数实现C=a+b ,不可以改变数据类型,如将c改为long int,关键是如何处理溢出问题
bool add (int a, int b,int *c)
{
*c=a+b;
return (a>0 && b>0 &&(*c<a || *c<b) || (a<0 && b<0 &&(*c>a || *c>b)));
}
x86架构的CPU处理加减法的溢出是这样的:
例如char类型表示的数据范围是-128 — 127,那么127+2=-127,-127-2=127;
两个int数相加,只有两个数的符号相同的时候才会溢出:
1,两个正数相加: a>0 && b>0 就是判断两数都是正数;如果没有溢出,则和必定大于a、b,反之溢出;
2,两负数相加: a<0 && b<0 判断两数都是负数;如果没有溢出,则和必定小于a、b,反之溢出;
综合起来就是:return (a>0 && b>0 && (*c<a||*c<b) || (a<0 && b<0 && (*c>a || *c>b)));
17:struct
bit
{
int a:3;
int b:2;
int
c:3;
};
int main()
{
bit s;
char *c=(char*)&s;
cout<<sizeof(bit)<<endl;
*c=0x99;
cout << s.a <<endl <<s.b<<endl<<s.c<<endl;
int a=-1;
printf("%x",a);
return 0;
}
输出为什么是
4
1
-1
-4
ffffffff
因为0x99在内存中表示为 100 11 001 , a = 001, b = 11, c = 100
当c为有符合数时, c = 100, 最高1为表示c为负数,负数在计算机用补码表示,所以c = -4;同理
b = -1;
当c为有符合数时, c = 100,即 c = 4,同理 b = 3
原码:将一个整数,转换成二进制,就是其原码。如单字节的5的原码为:0000 0101;-5的原码为1000 0101。
反码:正数的反码就是其原码;负数的反码是将原码中,除符号位以外,每一位取反。如单字节的5的反码为:0000 0101;-5的原码为1111 1010。
补码:正数的补码就是其原码;负数的反码+1就是补码。如单字节的5的补码为:0000 0101;-5的补码为1111 1011。
在计算机中,正数是直接用原码表示的,如单字节5,在计算机中就表示为:0000 0101。负数用补码表示,如单字节-5,在计算机中表示为1111 1011。
十进制数 | 符号位+ 二进制绝对值 的表示方式 | ones' complement | two's complement |
+7 | 0111 | 表示方式不变 | 表示方式不变 |
+6 | 0110 | 表示方式不变 | 表示方式不变 |
+5 | 0101 | 表示方式不变 | 表示方式不变 |
+4 | 0100 | 表示方式不变 | 表示方式不变 |
+3 | 0011 | 表示方式不变 | 表示方式不变 |
+2 | 0010 | 表示方式不变 | 表示方式不变 |
+1 | 0001 | 表示方式不变 | 表示方式不变 |
+0 | 0000 | 表示方式不变 | 表示方式不变 |
-0 | 1000 | 1111 | [1]0000 |
-1 | 1001 | 1110 | 1111 |
-2 | 1010 | 1101 | 1110 |
-3 | 1011 | 1100 | 1101 |
-4 | 1100 | 1011 | 1100 |
-5 | 1101 | 1010 | 1011 |
-6 | 1110 | 1001 | 1010 |
-7 | 1111 | 1000 | 1001 |
-8 | 超出4个bit所能表达范围 | 超出4个表达范围 | 1000 |
注: | 要设计硬件区分符号位,比较绝对值大小。 | 无需设计硬件比较大小,但零存在两种表示方法。 | 较好的解决上述问题 。由于零只有一种表达方式,所以,可以比别的方式多表达一个-8. |
改错:
#include <stdio.h>
int main(void)
{
int **p;
int arr[100];
p = &arr;
return 0;
}
解答:搞错了,是指针类型不同,
int **p; //二级指针
&arr; //得到的是指向第一维为100的数组的指针
#include <stdio.h>
int main(void)
{
int **p, *q;
int arr[100];
q = arr;
p = &q;
return 0;
}
19:下面这个程序执行后会有什么错误或者效果:
#define MAX 255
int main()
{
unsigned char A[MAX],i;//i被定义为unsigned char
for (i=0;i<=MAX;i++)
A[i]=i;
return 0;
} 解答:死循环加数组越界访问(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了..无限循环下去。
20:struct name1
{
char str;
short x;
int num;
}
struct name2
{
char str;
int num;
short x;
}
sizeof(struct name1)=8,sizeof(struct name2)=12
在第二个结构中,为保证num按四个字节对齐,char后必须留出3字节的空间;同时为保证整个结构的自然对齐(这里是4字节对齐),在x后还要补齐2个字节,这样就是12字节。
21:A.c 和B.c两个c文件中使用了两个相同名字的static变量,编译的时候会不会有问题?这两个static变量会保存到哪里(栈还是堆或者其他的)?
static的全局变量,表明这个变量仅在本模块中有意义,不会影响其他模块。他们都放在数据区,但是编译器对他们的命名是不同的。如果要使变量在其他模块也有意义的话,需要使用extern关键字。
22:
.struct s1 { int i: 8; int j: 4; int a: 3; double b; }; struct s2 { int i: 8; int j: 4; double b; int a:3; }; printf("sizeof(s1)= %d\n", sizeof(s1)); printf("sizeof(s2)= %d\n", sizeof(s2)); 输出答案:16, 24。。。想知道是为什么。。。。
2013-10-12
12:13
提问者采纳
计算结构体大小,按照如下原则 (1)获得最大字段大小;只针对语言级别的原生类型,比如char,int,double等;这个大小就是本结构体的对齐大小;比如你的s1和s2的对齐大小都是8 (2)从第一个字段开始,依次向最后一个字段扫描并累加; 在遇到对齐大小的字段之前,依次累加扫描到的字段大小; 在遇到对齐大小的字段之后,将之前的累加和进位为对齐大小,然后加上当前字段(即对齐字段);继续扫描; (3)全部扫描完后,如果累加的大小不是对齐大小的倍数,则按照对齐大小再进位一次,就是结构体大小; 比如s1,对齐大小为8,在扫描到b之前,累加的大小是4(int);扫描到b后,4进位为8,再加上b的大小,累加为16;所以sizeof(s1)为16 比如2,对齐大小为8,在扫描到b之前,累加的大小是4(int);扫描到b后,4进位为8,再加上b的大小,累加为16;扫描到a,累加大小为20;此时扫描结束,由于不是8的倍数,再进位为24
23:堆与栈的去区别
A. 申请方式不同
Stack由系统自动分配,而heap需要程序员自己申请,并指明大小。
B. 申请后系统的响应不同
stack:只要栈的剩余空间大于申请空间,系统就为程序提供内存,否则将抛出栈溢出异常
Heap:当系统收到程序申请时,先遍历操作系统中记录空闲内存地址的链表,寻找第一个大于所申请空间的堆结点,然后将该结点从空间结点链表中删 除,并将该结点的空间分配给程序。另外,大多数系统还会在这块内存空间中的首地址处记录本次分配的大小,以便于delete语句正确释放空间。而且,由于
找到的堆结点的大小不一定正好等于申请的大小,系统会自动将多余的那部分重新放入空闲链表。
C. 申请大小限制的不同
Stack:在windows下,栈的大小是2M(也可能是1M它是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
Heap:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
D. 申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。
E. 堆和栈中的存储内容
栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器 中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开
始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
24:含参数的宏与函数的优缺点
宏:
优点:在预处理阶段完成,不占用编译时间,同时,省去了函数调用的开销,运行效率高
缺点:不进行类型检查,多次宏替换会导致代码体积变大,而且由于宏本质上是字符串替换,它的定义很容易产生二义性,故可能会由于一些参数的副作用导致得出错误的结果。
函数:
优点:没有带参数宏可能导致的副作用,进行类型检查,计算的正确性更有保证。
缺点:函数调用需要参数、返回地址等的入栈、出栈开销,效率没有带参数宏高
PS:宏与内联函数的区别: 内联函数和宏都是在程序出现的地方展开,内联函数不是通过函数调用实现的,是在调用该函数的程序处将它展开(在编译期间完成的);不同的是:内联函数可以在编译期间完成诸如类型检测,语句是否正确等编译功能;宏就不具有这样的功能,而且宏展开的时间和内联函数也是不同的(在预编译期间展开)
25:7Windows程序的入口是哪里?写出Windows消息机制的流程
Windows程序的入口是WinMain()函数。
Windows应用程序消息处理机制:
A. 操作系统接收应用程序的窗口消息,将消息投递到该应用程序的消息队列中
B. 应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息,取出消息后,应用程序可以对消息进行一些预处理。
C. 应用程序调用DispatchMessage,将消息回传给操作系统。
D. 系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理。
26:请写出下列代码的输出内容
#include<stdio.h>
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
27、请找出下面代码中的所以错误 说明:以下代码是把一个字符串倒序,如“abcd”倒序后变为“dcba”
#include "string.h"
main()
{
char*src="hello,world";
char* dest=NULL;
int len=strlen(src);
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 <stdio.h>
#include <string.h>
main() {
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;
}
28:-1,2,7,28,,126请问28和126中间那个数是什么?为什么?
答案应该是4^3-1=63
规律是n^3-1(当n为偶数0,2,4) n^3+1(当n为奇数1,3,5)
答案:63 9
29:在c语言库函数中将一个字符转换成整型的函数是atool()吗,这个函数的原型是什么?
函数名: atol
功 能: 把字符串转换成长整型数
用 法: long atol(const char *nptr);
程序例:
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
long l;
char *str = "98765432";
l = atol(lstr);
printf("string = %s integer = %ld\n", str, l);
return(0);
}
30:
unsigned char *p1;
unsigned long *p2;
p1=(unsigned char *)0x801000;
p2=(unsigned long *)0x810000;
请问p1+5= ?0x801005; p2+5= ?0x801014;
p1+5=0x801005; p2+5=0x801014;
输出结果p1+5的值是801005,因为指针变量指向的值字符,加一表示指针向后移动一个字节,那么加5代表向后移动5个字节,所以输入801005 p2+5的值是801014,因为指针变量指向的长整形的,加一表示指针向后移动4个字节,那么加5代表向后移动20个字节,所以输入810014
相关文章推荐
- SQL经典面试题及答案
- 程序员的数学
- 2016 普华永道面试题
- Spark 和hadoop的一些面试题2(准备)
- 【.Net码农】GridView事件大全
- 【.Net码农】Gridview中Datakeys 通过主键取得各列的值。
- Android ListView 相关问题(面试常用)
- 面试心得
- 面试小谈
- [.Net码农]gridview再次绑定的时候 标题字体变大 怎么解决
- 面试题-Integer与int的种种比较你知道多少?
- 剑指offer-面试题20:顺时针打印矩阵
- 面试题11:数值的整数次方的计算
- 程序员理想中的工作环境 转自ewangplay
- 我是一个无聊的程序员(但是我以此为荣)
- 你想在职场上变身成为《欢乐颂》霸气女高管安迪吗?
- 简历投递
- 在职场中混,"讲演稿"的重要性
- 职场人打开工作邮箱的频率与公司业绩相关吗?
- 剑指offer-面试题18:树的子结构