int a[12]={"hello world"};
假设有一个数组int a[5],a[0]为数组的第一个元素,数组名a就是数组元素a[0]的地址,即对于一维数组 &a[0]==a,他们指向同样的一个内存单元。
对于一维数组而言,&a[0],a,&a都是指向同一个内存地址,虽然他们所指向的地址是相同的,但是他们本身的类型是不一样的,a[0]='h'是一个char类型的变量,&a[0]是一个char *类型的指针。对&a[0]进行加一操作会指向a[1]。但是a是一个数组变量,&a是一个char *[12]类型的指针,对&a进行+1操作会跳过12个char类型的地址。
对于二维数组而言:
char str[][10] = {"hello","world","there"};
printf("0x%-10x",str); // 0
printf("0x%-10x",str[0]); // 0
printf("0x%-10x",str[1]); // 10
printf("0x%-10x",&str[0][0]); // 0
printf("0x%-10x",&str[0][0]+1); // 1
printf("0x%-10x",&str); // 1
printf("0x%-10x",&str +1); // 30
// 像main()函数那样来传递字符串参数
void my_copy(char **s, int n)
{
/*do anything*/
}
int main()
{
char *str[] = {"hello","world","there"};
my_copy( ( char *[] ){str[0],str[1],str[2],NULL});//C99
//or
char *p[]={ str[0],str[1],str[2],NULL };//C89
my_copy(p,3);
}
数组指针
数组指针是一个指向数组的指针变量,通常用来指向数组的首地址,也可以指向数组的元素。
为了编写将二维数组作为参数的函数,数组名被其视为地址,因此,相应的形式参数是一个指针,就像一维数组一样.例如有下面一段代码:
int data[3][4] = {{1,2,3,4}, {5,6,7,8},{1,2,3,5}};
int total = sum(data,3);
那么sum函数的原型应该是什么样子的呢?data是一个数组名,这个数组有3个元素.第一个元素本身是一个数组,由4个int值组成.因此data的类型是指向4个int组成的数组的指针.正确的原型如下:int sum(int (*ar2)[4],int size);
还有另外的一种格式,这种格式与上述的原型含义一致,但是可读性更强int sum(int ar2[][4],int size);
- 要对绝对地址 0x100000赋值,我们可以使用*(unsigned int *)0x100000 = 1234;那么如果想让程序跳转到绝对地址0x100000去执行,那么应该怎么做?
答:*((void(*)(void))0x100000)(void)
;首先要将0x100000强制转换成函数指针,即:(void(*)(void))0x100000
然后再调用它:*(void(*)(void))0x100000(void)
; 或者使用一更加直观的方式:typedef void(*)(void) voidFuncPtr;
*((voidFuncPtr)0x100000)(void);
- 分析下面这段程序
void getmemory(char *p)
{
p=(char *)malloc(100);
strcpy(p,"hello world");
}
int main()
{
char *str = NULL;
getmemory(str);
printf("%s\n",str);
free(str);
return 0;
}
在这程序中存在一个最大的问题是getmemory()中的malloc并不能够返回动态内存,free对str的操作是很危险的,此时str仍然是指向NULL的,并不是malloc返回的地址。应该修改为二级指针,如下:
void getmemory(char **p)
{
*p = (char *)malloc(100);
strcpy(p,"hello world");
}
int main()
{
char *str = NULL;
getmemory(&str);
printf ("%s\n",str);
free(str);
return 0;
}
- 分析下面这段程序
typedef struct AA
{
int b1:5;
int b2:2;
}AA
int main(int argc, char const *argv[])
{
AA aa;
char cc[100];
strncpy(cc,"0123456789",10);
memcpy(&aa,cc,sizeof(AA));
printf("%d:%d\n",aa.b1,aa.b2);
return 0;
}
解析:输出为 -16和1,首先sizeof(AA)的大小为4,b1和b2分别占据了5个bit和2个bit。经过strcpy和memcpy之后aa的4个字节所存放的值是0,1,2,3的ASCII码,即00110000,00110001,00110010,00110011,所以,最后一步:显式的是这4个字节的前5位和之后的2位,分别是:10000和01,因为int是由正负之分的,所以结果为-16和1。
- 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR 60 * 60 * 24 * 365UL
预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。由于这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
- 编写一个标准的的宏MIN,将这个宏输入的两个参数返回较小的一个。
#define MIN(A,B) do{(A)<=(B)?(A):(B)}while(0)
- 预处理器标识#error的作用是什么?
#error命令是C/C++语言的预处理命令之一,当预处理器预处理到#error命令时将停止编译并输出用户自定义的错误消息。其目的就是保证程序是按照你所设想的那样进行编译的。例如:
程序中往往有很多的预处理指令
#ifdef XXX
...
#else
#endif
当程序比较大时,往往有些宏定义是在外部指定的(如makefile),或是在系统头文件中指定的,当你不太确定当前是否定义了 XXX 时,就可以改成如下这样进行编译:
#ifdef XXX
...
#error "XXX has been defined"
#else
#endif
这样,如果编译时出现错误,输出了XXX has been defined,表明宏XXX已经被定义了。
7.c和c++中的struct有什么不同?
c和c++中struct的主要区别是c中的struct不可以含有成员函数,而c++中的struct可以。c++中struct和class的主要区别在于默认的存取权限不同,struct默认为public,而class默认为private
8 .用两个队列实现一个栈的功能
答:设两个栈分别为A,B, 一开始均为空。入队: 将新元素push入栈A; 出队: (1)判断栈B是否为空; (2)如果不为空,则将栈A中所有元素依次pop出并push到栈B; (3)将栈B的栈顶元素pop出;这样实现的队列入队和出队的平摊复杂度都还是O(1)。所以的入栈操作都在A上进行,所有的出栈操作都在B上进行。
- 对于一个频繁使用的短小函数,在C语言中应用什么实现,在C++中应用什么实现?
答案:c用宏定义,c++用inline;
- 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf(" Area = %f", area);
return area;
}
这个函数有太多的错误了,以至让人不知从何说起了:
1). ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
2). ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
3). 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
4). 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。
- Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:
#define dPS struct s *
typedef struct s * tPS;
以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:
dPS p1,p2;
tPS p3,p4;
第一个扩展为struct s * p1, p2;
上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。第二个例子正确地定义了p3 和p4 两个指针。
-
*100 = 25;
它看上去像是把 25 赋值给了地址为100的内存单元,但是这条语句实际上是非法的,因为字面值 100 的类型是整型,而间接访问操作只能作用于指针类型表达式。如果你确实想把 25 存储于位置 100,你必须使用强制类型转换。*(int *)100 = 25;
- 浮点数和0比较时使用<0.000001(6个零)&&>-0.000001
c和c++对有效数位的要求是,float至少32位,double至少48位且不少于float,long double至少和double一样多(这里指的是二进制数)。通常来说,float为32位,double为64位,long double为80、96、128位。大多数情况下,编译器为float分配4字节,double则分配8字节。
float32位组成:最高位的1位为符号位,接着指数位共8位,指数范围-127 ~ +128(补码形式),然后尾数位共23位。那为什么float型计算机的显示结果只显示6位有效数字呢?那是因为,计算机内部以二进制法存储数据,尾数23即共 2^23 个有效数字,那么转化成十进制就是log10( 2^23 )=6.92,即6 ~ 7位有效数字,计算机保证至少6位有效数字的精度。同理,64位数的double1位符号位,11位指数位,指数范围为-1023 ~ +1024,52位尾数,有效数字为15 ~ 16,保证有效数字为15位精度。但是,我们会发现,无论用float还是double,我们用cout输出值最多显示6位有效数字。那是因为c++cout默认输出的精度是6位(四舍五入),如果想显示多于6位的精度,那么可以输入头文件#include <iomanip>,然后在输出语句之前插入cout << setprecision(x),x是要显示的精度。假如要显示的精度超出原来数字的实际精度,那么就会有超出部分的误差,例如:
float a =2.12351f;
cout << setprecision(10)<<a<<endl;//输出结果2.123509884
又如:
float a= 2.34E+22f;
float b = a+1.0f;
cout<<"a="<<a<<endl;//a=23400001102275227418424.000000
cout<<"b-a="<<b-a<<endl;//b-a=0.000000;改程序讲数字加1,然后减去原来的数字,结果应该为1,但是输出却是0;
从a的输出可以看出只有前7位是精确地,再往后就是110227...这些都是不精确的数字,因此从第七位往后不论加减都是没有意义的.
问题在于2.34e+22是一个小数点左边有23位的数字.再加上1,就是在第23位加1,但是float类型只能够表示数字中的前6位或者是7位,因此修改第23位对这个值不会有任何的影响.
signed、unsigned关键字
我们知道计算机底层只认识 0、 1.任何数据到了底层都会变计算转换成 0、 1.那负数怎么存储呢?肯定这个“-”号是无法存入内存的,怎么办?很好办,做个标记。把基本数据类型的最高位腾出来,用来存符号,同时约定如下:最高位如果是 1,表明这个数是负数,其值为除最高位以外的剩余位的值添上这个“-”号;如果最高位是 0,表明这个数是正数,其值为除最高位以外的剩余位的值。在C/C++中,为了避免同一个文件被include多次,有两种方式:一种是#ifndef方式,一种是#pragma once方式(在头文件的最开始加入)。
#ifndef的是方式是受C/C++语言标准支持。#ifndef方式依赖于宏名不能冲突。它不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。缺点是如果不同头文件中的宏名不小心”碰撞”,可能就会导致你看到头文件明明存在,编译器却硬说找不到声明的状况。由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,#ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式。
#pragma once一般由编译器提供保证:同一个文件不会被包含多次。这里所说的”同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。无法对一个头文件中的一段代码作#pragma once声明,而只能针对文件。此方式不会出现宏名碰撞引发的奇怪问题,大型项目的编译速度也因此提供了一些。缺点是如果某个头文件有多份拷贝,此方法不能保证它们不被重复包含。在C/C++中,#pragma once是一个非标准但是被广泛支持的方式。
#pragma once方式产生于#ifndef之后。#ifndef方式受C/C++语言标准的支持,不受编译器的任何限制;而#pragma once方式有些编译器不支持(较老编译器不支持,如GCC 3.4版本之前不支持#pragma once),兼容性不够好。#ifndef可以针对一个文件中的部分代码,而#pragma once只能针对整个文件。