当前位置:文档之家› 指针学习

指针学习

指针学习
指针学习

一.指针是一个特殊的变量,

1. 指针的类型

它里面存储的数值被解释成为内存里的一个地址。要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区。让我们分别说明。先声明几个指针放着做例子:

例一:

(1)int*ptr;

(2)char*ptr;

(3)int**ptr;

(4)int(*ptr)[3];

(5)int*(*ptr)[4];

从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型:

(1)int*ptr;//指针的类型是int*

(2)char*ptr;//指针的类型是char*

(3)int**ptr;//指针的类型是int**

(4)int(*ptr)[3];//指针的类型是int(*)[3]

(5)int*(*ptr)[4];//指针的类型是int*(*)[4]

怎么样?找出指针的类型的方法是不是很简单?

2. 指针所指向的类型

当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:

(1)int*ptr;//指针所指向的类型是int

(2)char*ptr;//指针所指向的的类型是char

(3)int**ptr;//指针所指向的的类型是int*

(4)int(*ptr)[3];//指针所指向的的类型是int()[3]

(5)int*(*ptr)[4];//指针所指向的的类型是int*()[4]

在指针的算术运算中,指针所指向的类型有很大的作用。

指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C越来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起

书来前后矛盾,越看越糊涂。

3.指针的值,或者叫指针所指向的内存区或地址

指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。

指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。

4. 总结一下

以后每遇到一个指针,都应该问问(指针的四要素):

1.这个指针的类型是什么?

2.指针指的类型是什么?

3.该指针指向了的内存区?

4.指针本身所占据的内存区?

这里指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。

二. 指针的算术运算

1. 指针可以加上或减去一个整数

指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。例如:

例二:

1、chara[20];

2、int*ptr=a;

...

3、ptr++;

在上例中,指针ptr的类型是int*,它指向的类型是int,它被初始化为指向整形变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr的值加上了sizeof(int),在32位程序中,是被加上了4。由于地址是用字节做单位的,故ptr所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。由于char类型的长度是一个字节,所以,原来ptr是指向数组a的第0号单元开始的四个字节,

此时指向了数组a中从第4号单元开始的四个字节。

我们可以用一个指针和一个循环来遍历一个数组,看例子:

例三:

int array[20];

int *ptr=array;

...

//此处略去为整型数组赋值的代码。

...

for(i=0;i <20;i++)

{

(*ptr)++;

ptr++;

}

这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1,所以每次循环都能访问数组的下一个单元。

再看例子:

例四:

1、char a[20];

2、int *ptr=a;

...

...

3、ptr+=5;

在这个例子中,ptr被加上了5,编译器是这样处理的:将指针ptr的值加上5乘sizeof(int),在32位程序中就是加上了5乘4=20。由于地址的单位是字节,故现在的ptr所指向的地址比起加5后的ptr所指向的地址来说,向高地址方向移动了20个字节。在这个例子中,没加5前的ptr指向数组a的第0号单元开始的四个字节,加5后,ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。

如果上例中,ptr是被减去5,那么处理过程大同小异,只不过ptr的值是被减去5乘sizeof(int),新的ptr指向的地址将比原来的ptr所指向的地址向低地址方向移动了20个字节。

2. 总结一下

一个指针ptrold加上一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值增加了n乘sizeof(ptrold所指向的类型)个字节。就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向高地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。

一个指针ptrold减去一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值减少了n乘sizeof(ptrold所指向的类型)个字节,就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向低地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。

三. 运算符&和*

这里&是取地址运算符,*是...书上叫做"间接运算符"。

&a的运算结果是一个指针,指针的类型是a的类型加个*,指针所指向的类型是a的类型,指针所指向的地址嘛,那就是a的地址。

*p的运算结果就五花八门了。总之*p的结果是p所指向的东西,这个东西有这些特点:它的类型是p 指向的类型,它所占用的地址是p所指向的地址。

例五:

int a=12;

int b;

int *p;

int **ptr;

p=&a;

//&a的结果, 是一个指针,类型是int*,指向的类型是int,指向的地址是a的地址。

*p=24;

//*p的结果,在这里它的类型是int,它所占用的地址是p所指向的地址,显然,*p就是变量a。

ptr=&p;

//&p的结果是个指针,该指针的类型是p的类型加个*,在这里是int **。该指针所指向的类型是p的类型,这里是int*。该指针所指向的地址就是指针p自己的地址。

*ptr=&b;

//*ptr是个指针,&b的结果也是个指针,且这两个指针的类型和所指向的类型是一样的,所以用&b来给*ptr 赋值就是毫无问题的了。

**ptr=34;

//*ptr的结果是ptr所指向的东西,在这里是一个指针,对这个指针再做一次*运算,结果就是一个int类型的变量。

四. 指针表达式

一个表达式的最后结果如果是一个指针,那么这个表达式就叫指针表式。

下面是一些指针表达式的例子:

例六:

int a,b;

int array[10];

int *pa;

pa=&a; //&a是一个指针表达式。

int **ptr=&pa; //&pa也是一个指针表达式。

*ptr=&b; //*ptr和&b都是指针表达式。

pa=array;

pa++; //这也是指针表达式。

例七:

char *arr[20];

char **parr=arr; //如果把arr看作指针的话,arr也是指针表达式

char *str;

str=*parr; //*parr是指针表达式

str=*(parr+1); //*(parr+1)是指针表达式

str=*(parr+2); //*(parr+2)是指针表达式

由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。

五. 数组和指针的关系

数组的数组名其实可以看作一个指针。看下例:

例八:

int array[10]={0,1,2,3,4,5,6,7,8,9},value;

...

...

value=array[0]; //也可写成:value=*array;

value=array[3]; //也可写成:value=*(array+3);

value=array[4]; //也可写成:value=*(array+4);

上例中,一般而言数组名array代表数组本身,类型是int[10],但如果把array看做指针的话,它指向数组的第0个单元,类型是int*,所指向的类型是数组单元的类型即int。因此*array等于0就一点也不奇怪了。同理,array+3是一个指向数组第3个单元的指针,所以*(array+3)等于3。其它依此类推。

例九:

char *str[3] = {"Hello,thisisasample! ", "Hi,goodmorning. ", "Helloworld " };

char s[80];

strcpy(s,str[0]); //也可写成strcpy(s,*str);

strcpy(s,str[1]); //也可写成strcpy(s,*(str+1));

strcpy(s,str[2]); //也可写成strcpy(s,*(str+2));

上例中

str是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str当作一个指针的话,它指向数组的第0号单元,它的类型是char**,它指向的类型是char*。

*str也是一个指针,它的类型是char*,它所指向的类型是char,它指向的地址是字符串"Hello,thisisasample! "的第一个字符的地址,即'H '的地址。str+1也是一个指针,它指向数组的第1号单元,它的类型是char**,它指向的类型是char*。

*(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向"Hi,goodmorning. "的第一个字符'H ',等等。

下面总结一下数组的数组名的问题。声明了一个数组TYPE array[n],则数组名称array就有了两重含义:第一,它代表整个数组,它的类型是TYPE[n];第二,它是一个指针,该指针的类型是TYPE*,该

指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0号单元,该指针自己占有单独的内存区,注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的(因为这里的array相当于一个指针常量,可以访问,不能修改)。

在不同的表达式中数组名array可以扮演不同的角色。

在表达式sizeof(array)中,数组名array代表数组本身,故这时sizeof函数测出的是整个数组的大小。

在表达式*array中,array扮演的是指针,因此这个表达式的结果就是数组第0号单元的值。sizeof(*array)测出的是数组单元的大小。

表达式array+n(其中n=0,1,2,....。)中,array扮演的是指针,故array+n的结果是一个指针,它的类型是TYPE*,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。

例十

int array[10];

int (*ptr)[10];

ptr = &array;:

上例中ptr是一个指针,它的类型是int(*)[10],他指向的类型是int[10] ,我们用整个数组的首地址来初始化它。在语句ptr=&array中,array代表数组本身。

本节中提到了函数sizeof(),那么我来问一问,sizeof(指针名称)测出的究竟是指针自身类型的大小呢还是指针所指向的类型的大小?答案是前者。例如:

int(*ptr)[10];

则在32位程序中,有:

sizeof(int(*)[10])=4

sizeof(int[10])=40

sizeof(ptr)=4

实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。

六. 指针和结构类型的关系

可以声明一个指向结构类型对象的指针。

例十一:

struct MyS truct

{

int a;

int b;

int c;

}

MyS truct ss={20,30,40};

//声明了结构对象ss,并把ss的三个成员初始化为20,30和40。

MyS truct *ptr=&ss;

//声明了一个指向结构对象ss的指针。它的类型是MyStruct*,它指向的类型是MyStruct。

int *pstr=(int*)&ss;

//声明了一个指向结构对象ss的指针。但是它的类型和它指向的类型和ptr是不同的。

请问怎样通过指针ptr来访问ss的三个成员变量?

答案:

ptr-> a;

ptr-> b;

ptr-> c;

又请问怎样通过指针pstr来访问ss的三个成员变量?

答案:

*pstr;//访问了ss的成员a。

*(pstr+1); //访问了ss的成员b。

*(pstr+2); //访问了ss的成员c。

虽然我在我的MSVC++6.0上调式过上述代码,但是要知道,这样使用pstr来访问结构成员是不正规的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元:

例十二:

int array[3]={35,56,37};

int *pa=array;

通过指针pa访问数组array的三个单元的方法是:

*pa;//访问了第0号单元

*(pa+1);//访问了第1号单元

*(pa+2);//访问了第2号单元

从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。

所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个"填充字节",这就导致各个成员之间可能会有若干个字节的空隙。

所以,在例十二中,即使*pstr访问到了结构对象ss的第一个成员变量a,也不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a和成员b之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节,嘿,这倒是个不错的方法。

通过指针访问结构成员的正确方法应该是象例十二中使用指针ptr的方法。

七. 指针和函数的关系

可以把一个指针声明成为一个指向函数的指针。

int (*pfun1)(char*,int);

....

....

int a=(*pfun1)( "abcdefg ",7);//通过函数指针调用函数。

可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。

例十三:

int fun(char*);

int a;

char str[]= "abcdefghijklmn ";

a=fun(str);

...

...

int fun(char*s)

{

int num=0;

for(int i=0;i!=‘\0’;i++)

{

num+=*s;

s++;

}

return num;

}

这个例子中的函数fun统计一个字符串中各个字符的ASCII码值之和。前面说了,数组的名字也是一个指针。在函数调用中,当把str作为实参传递给形参s后,实际是把str的值传递给了s,s所指向的地址就和str所指向的地址一致,但是str和s各自占用各自的存储空间。在函数体内对s进行自加1运算,并不意味着同时对str进行了自加1运算。

八. 指针类型转换

当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式。在我们前面所举的例子中,绝大多数情况下,指针的类型和指针表达式的类型是一样的,指针所指向的类型和指针表达式所指向的类型是一样的。

例十四:

1、float f=12.3;

2、float *fptr=&f;

3、int *p;

在上面的例子中,假如我们想让指针p指向实数f,应该怎么搞?是用下面的语句吗?

p=&f;

不对。因为指针p的类型是int*,它指向的类型是int。表达式&f的结果是一个指针,指针的类型是float*,它指向的类型是float。两者不一致,直接赋值的方法是不行的。至少在我的MSVC++6.0上,对指针的赋值语句要求赋值号两边的类型一致,所指向的类型也一致,其它的编译器上我没试过,大家可以试试。为了实现我们的目的,需要进行"强制类型转换":

p=(int*)&f;

如果有一个指针p,我们需要把它的类型和所指向的类型改为TYEP*TYPE,那么语法格式是:(TYPE*)p;

这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE*,它指向的类型是TYPE,它指向

的地址就是原指针指向的地址。而原来的指针p的一切属性都没有被修改。

一个函数如果使用了指针作为形参,那么在函数调用语句的实参和形参的结合过程中,也会发生指针类型的转换。

例十五:

void fun(char*);

int a=125,b;

fun((char*)&a);

...

...

void fun(char*s)

{

char c;

c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;

c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;

}

}

注意这是一个32位程序,故int类型占了四个字节,char类型占一个字节。函数fun的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗?在函数调用语句中,实参&a的结果是一个指针,它的类型是int*,它指向的类型是int。形参这个指针的类型是char*,它指向的类型是char。这样,在实参和形参的结合过程中,我们必须进行一次从int*类型到char*类型的转换。结合这个例子,我们可以这样来想象编译器进行转换的过程:编译器先构造一个临时指针char*temp,然后执行temp=(char*)&a,最后再把temp的值传递给s。所以最后的结果是:s的类型是char*,它指向的类型是char,它指向的地址就是a的首地址。

我们已经知道,指针的值就是指针指向的地址,在32位程序中,指针的值其实是一个32位整数。那可不可以把一个整数当作指针的值直接赋给指针呢?就象下面的语句:

unsigned int a;

TYPE *ptr; //TYPE是int,char或结构类型等等类型。

...

...

a=20345686;

ptr=20345686; //我们的目的是要使指针ptr指向地址20345686(十进制)

ptr=a; //我们的目的是要使指针ptr指向地址20345686(十进制)

编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗?不,还有办法:unsigned int a;

TYPE *ptr; //TYPE是int,char或结构类型等等类型。

...

...

a=某个数,这个数必须代表一个合法的地址;

ptr=(TYPE*)a;//呵呵,这就可以了。

严格说来这里的(TYPE*)和指针类型转换中的(TYPE*)还不一样。这里的(TYPE*)的意思是把无符号整数a 的值当作一个地址来看待。上面强调了a的值必须代表一个合法的地址,否则的话,在你使用ptr的时候,就会出现非法操作错误。

想想能不能反过来,把指针指向的地址即指针的值当作一个整数取出来。完全可以。下面的例子演

示了把一个指针的值当作一个整数取出来,然后再把这个整数当作一个地址赋给一个指针:例十六:

int a=123,b;

int *ptr=&a;

char *str;

b=(int)ptr;//把指针ptr的值当作一个整数取出来。

str=(char*)b;//把这个整数的值当作一个地址赋给指针str。

现在我们已经知道了,可以把指针的值当作一个整数取出来,也可以把一个整数值当作地址赋给一个指针。

九. 指针的安全问题

看下面的例子:

例十七:

char s= 'a ';

int *ptr;

ptr=(int*)&s;

*ptr=1298;

指针ptr是一个int*类型的指针,它指向的类型是int。它指向的地址就是s的首地址。在32位程序中,s占一个字节,int类型占四个字节。最后一条语句不但改变了s所占的一个字节,还把和s相临的高地址方向的三个字节也改变了。这三个字节是干什么的?只有编译程序知道,而写程序的人是不太可能知道的。也许这三个字节里存储了非常重要的数据,也许这三个字节里正好是程序的一条代码,而由于你对指针的马虎应用,这三个字节的值被改变了!这会造成崩溃性的错误。

让我们再来看一例:

例十八:

1、char a;

2、int *ptr=&a;

...

...

3、ptr++;

4、*ptr=115;

该例子完全可以通过编译,并能执行。但是看到没有?第3句对指针ptr进行自加1运算后,ptr指向了和整形变量a相邻的高地址方向的一块存储区。这块存储区里是什么?我们不知道。有可能它是一个非常重要的数据,甚至可能是一条代码。而第4句竟然往这片存储区里写入一个数据!这是严重的错误。所以在使用指针时,程序员心里必须非常清楚:我的指针究竟指向了哪里。在用指针访问数组的时候,也要注意不要超出数组的低端和高端界限,否则也会造成类似的错误。

在指针的强制类型转换:ptr1=(TYPE*)ptr2中,如果sizeof(ptr2的类型)大于sizeof(ptr1的类型),那么在使用指针ptr1来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2的类型)小于sizeof(ptr1的类型),那么在使用指针ptr1来访问ptr2所指向的存储区时是不安全的。至于为什么,读者结合例十七来想一想,应该会明白的。

十. 右左法则

C语言所有复杂的指针声明,都是由各种声明嵌套构成的。如何解读复杂指针声明呢?右左法则是一个既著名又常用的方法。不过,右左法则其实并不是C标准里面的内容,它是从C标准的声明规定中归纳出来的方法。C标准的声明规则,是用来解决如何创建声明的,而右左法则是用来解决如何辩识一个声明的,两者可以说是相反的。右左法则的英文原文是这样说的:

The right-left rule: Start reading the declaration from the innermost parentheses, go r ight, and then go left. When you encounter parentheses, the direction should be re versed. Once everything in the parentheses has been parsed, jump out of it. Contin ue till the whole declaration has been parsed.

这段英文的翻译如下:

右左法则:首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。

笔者要对这个法则进行一个小小的修正,应该是从未定义的标识符开始阅读,而不是从括号读起,之所以是未定义的标识符,是因为一个声明里面可能有多个标识符,但未定义的标识符只会有一个。

现在通过一些例子来讨论右左法则的应用,先从最简单的开始,逐步加深:

int (*func)(int *p);

首先找到那个未定义的标识符,就是func,它的外面有一对圆括号,而且左边是一个*号,这说明func是一个指针,然后跳出这个圆括号,先看右边,也是一个圆括号,这说明(*func)是一个函数,而func是一个指向这类函数的指针,就是一个函数指针,这类函数具有int*类型的形参,返回值类型是int。

int (*func)(int *p, int (*f)(int*));

func被一对括号包含,且左边有一个*号,说明func是一个指针,跳出括号,右边也有个括号,那么func 是一个指向函数的指针,这类函数具有int *和int (*)(int*)这样的形参,返回值为int类型。再来看一看func的形参int (*f)(int*),类似前面的解释,f也是一个函数指针,指向的函数具有int*类型的形参,返回值为int。

int (*func[5])(int *p);

func右边是一个[]运算符,说明func是一个具有5个元素的数组,func的左边有一个*,说明func的元素

是指针,要注意这里的*不是修饰func的,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合,因此*修饰的是func[5]。跳出这个括号,看右边,也是一对圆括号,说明func数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,返回值类型为int。

int (*(*func)[5])(int *p);

func被一个圆括号包含,左边又有一个*,那么func是一个指针,跳出括号,右边是一个[]运算符号,说明func是一个指向数组的指针,现在往左看,左边有一个*号,说明这个数组的元素是指针,再跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针。总结一下,就是:func是一个指向数组的指针,这个数组的元素是函数指针,这些指针指向具有int*形参,返回值为int类型的函数。

int (*(*func)(int *p))[5];

func是一个函数指针,这类函数具有int*类型的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。

要注意有些复杂指针声明是非法的,例如:

int func(void) [5];

func是一个返回值为具有5个int元素的数组的函数。但C语言的函数返回值不能为数组,这是因为如果允许函数返回值为数组,那么接收这个数组的内容的东西,也必须是一个数组,但C语言的数组名是一个右值,它不能作为左值来接收另一个数组,因此函数返回值不能为数组。

int func[5](void);

func是一个具有5个元素的数组,这个数组的元素都是函数。这也是非法的,因为数组的元素除了类型必须一样外,每个元素所占用的内存空间也必须相同,显然函数是无法达到这个要求的,即使函数的类型一样,但函数所占用的空间通常是不相同的。

作为练习,下面列几个复杂指针声明给读者自己来解析,答案放在第十章里。

int (*(*func)[5][6])[7][8];

int (*(*(*func)(int *))[5])(int *);

int (*(*func[7][8][9])(int*))[5];

实际当中,需要声明一个复杂指针时,如果把整个声明写成上面所示的形式,对程序可读性是一大损害。应该用typedef来对声明逐层分解,增强可读性,例如对于声明:

int (*(*func)(int *p))[5];

可以这样分解:

typedef int (*PARA)[5];

typedef PARA (*func)(int *);

这样就容易看得多了。

好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。

十一for example

((char*)((type *) 0 + 1) - (char*)((type *) 0))

表达式首先把0地址强制转换为type*并加1,这个是逻辑加,其实偏移了sizeof(type)个字节,最后把偏移后的值和0地址转换为char*,指针逻辑相减,得到这两个指针之间存放多少个其类型char。能求出type占多少字节是假设现在32位的编译器sizeof(char)= 1.在现在常用的编译器确实是这样。假如以后sizeof(char)!=1了,这个表达式就不能用了。就是可移植性不高。

假如你转化为int*,指针相减就是求偏移指针和0地址可以存放多少个int类型,显然不对。32位计算机sizeof(int)=4,它们之间的偏移还不一定是4的倍数。不过你可以试试

#define TSIZEOF(type) ((unsigned int)((type *) 0 + 1) - (unsigned int)((type *) 0))看看

int a=8.0/3;

.

8.0是double类型(C规定,程序中出现的任何无后缀小数都为double类型,

而出现的任何无后缀整数都为int型,在写程序时一定要记住),而3是int型。

除法运算可否在两种不同类型的变量之间进行呢?显然是不行的。因此,这里就需要转换。

我们知道,double型可表示的范围远远大于long型。而C和C++

在这类转换中的原则就是,

范围小的类型向范围大的类型转换。所以,3被转换成了double值3.0,然后做除法。

8.0/3.0=2.666666……

如果这时这个值被赋给一个double了,那么自然什么事都没有,double得到了这个小数,

万事和谐……可惜我们这里没有这么和谐了,这个double值被赋给了一个int……

我刚才说什么来着?隐式类型转换只能从小到大转,可是现在怎么从大到小了呢?

不急。你可以先编译一下这个语句,你会得到这样一个警告:

[点击此处复制到剪贴板] [ - ]CODE:

warning C4244: “初始化”: 从“double”转换到“int”,可能丢失数据

.

因为double比int要大,这样转换自然容易丢失数据。想象一下不是2.666……,

而是1e200,自然int就溢出了,你什么也得不到。这里只是警告,对于严格的编译器,

甚至会把它当作一个错误。

那么怎么办呢?这时我们就需要强制类型转换了。强制类型转换强迫编译器将一个

类型转换成较大的同一类类型。比如double到int,再比如int到char,这样,

我们就可以很简单地避免警告了:

[点击此处复制到剪贴板] [ - ]CODE:

在内存这座大厦里,到底是怎么可以方便快捷地找到想要的人的呢?

其实内存里面所有的房间都被编上了一个号码,这个号码就是所谓的地址了。

比如CPU老总要教训一下收购部的小伙子,他可以对内存总管说“兄弟,你把住在1号的那个小子送过来”,这样内存就寻找1号房间,然后将一号房间里面的小伙子

拎着扔给CPU老总(-_-!!!),教训完了,CPU要给小伙子换个工作,于是,

CPU又对内存说“把这个小伙子放到3号房间吧”,于是内存接住CPU

扔回的小伙子,

安置到了3号房间…………

呵呵呵,不搞怪了。大家明白,只要有了一个地址,就可以访问到特定的内存。

这就是指针。指针其实和刀差不多,同样是刀,可以切菜,可以杀牛,可以做手术,

可以杀人——慢!大家有没有想到?做手术的刀和切菜杀牛乱七八糟的刀其实都是不同的。

虽然都是刀,但有这样那样的区别!对,指针也是这样,因为用途不同(可能指向一个int啊,

也可能指向一个double啊),所以指针也分很多种。这些指针本质上都是一样的

(都是内存的地址嘛),如果指向一个地方,那么值也是一样的(同一个地方的地址自然

只有一个咯)。问题来了。刀还可以从形状成色看出来是干什么的,但单纯一个指针,

你怎么看出来它指着的是什么东西?除了从声明时候的类型看出以外,具体的使用时,

不同类型的指针之间到底会有什么差别呢?

我们来做一个实验。这里需要大家理解类型转换。看过上面对类型转换的介绍,

理解这个实验应该不难。

[点击此处复制到剪贴板] [ - ]CODE:

int a[4]={1,2,3,4};

char *pt=(char*)&a;

printf("%d %d",*(a+1),*(int*)(pt+4));

.

大家可以运行一下这段程序,看看输出多少。如果大家没有输错的话,结果应该是2,2。

0x4000: 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00

.

这里,0x3FFC位置的,就是pt了。注意到了吗?虽然pt指向的是char型的变量,

但是pt仍然是四个字节长,因为是地址嘛。希望看到了内存布局,你能更加理解

“指针和指针之间实质上是一样”这句话。看,x3FFC开始的四个字节的值是0x00004000

(注意:X86的计算机,字节是倒着摆的,所以我们要正过来看)。

正好是数组首元素所在的位置。

然后我们看*(a+1),a的类型是int *const,a+1又是什么呢?C规定,

将一个指针加上一个数字,那么这个指针的值会递增这个数字乘以指针指向元素的大小。

晕了么?解释一下你就明白了。

int型是四个字节。如果你给指针加一,指针变成了0x4001的话,那int型不是被破坏了么?

因为如果你在这个地址写一个int的话,那么肯定有三个字节写到了第一个元素里,

一个字节写到了第二个元素里,估计离程序崩溃也就不远了。所以这里一次加了

1*sizeof(int)=1*4=4个字节。1代表加的数字,而4就是int的长度了。OK,现在和谐了,

a+1的值为0x4004,从图书可以很清晰地看出,0x4004是数组第二个元素的首地址。

于是我们将0x4004处的值用地址操作符(*)取出来,交给printf 输出,

我们就得到了第一个值2。

再来看*(int*)(pt+4)。pt是一char型的指针。现在值仍然是

0x4000,

因为char型大小为一个字节,所以pt+4其实就是直接相加,其值为

0x4004。

但是pt是指向char的指针啊,就算加了4,其结果仍然是指向char型的指针。

这个时候如果取数的话,只能取到一位,这不行啊!于是,我们使用强制类型转换,

将计算结果0x4004强制转换成了指向int型的指针(注意指针本身的值并没有改变,

仍然是0x4004),再取出来,这里的转换是char* => int*。OK,仍然

初中信息学竞赛练习题

一、单选 1、关于计算机内存下面的说法哪个是正确的: A)随机存储器(RAM)的意思是当程 序运行时,每次具体分配给程序的 内存位置是随机而不确定的。 B)1MB内存通常是指1024*1024字节 大小的内存。 C)计算机内存严格说来包括主存 (memory)、高速缓存(cache)和 寄存器(register)三个部分。 D)一般内存中的数据即使在断电的情 况下也能保留2个小时以上。 2、关于CPU下面哪个说法是正确的: A)CPU全称为中央处理器(或中央处 理单元)。 B)CPU可以直接运行汇编语言。 C)同样主频下,32位的CPU比16位 的CPU运行速度快一倍。 D)CPU最早是由Intel公司发明的。 3. 下列网络上常用的名字缩写对应的中文解释错误的是()。 A. WWW(World Wide Web):万维网。 B. URL(Uniform Resource Locator):统一资源定位器。 C. HTTP(Hypertext Transfer Protocol):超文本传输协议。 D. FTP(File Transfer Protocol):快速传输协议。 E. TCP(Transfer Control Protocol):传输控制协议。 4. 设A=true,B=false,C=true, D=false,以下逻辑运算表达式值为真的是()。 A. (A∧B)∨(C∧D∨?A) B. ((?A∧B)∨C)∧?D C. (B∨C∨D)∧D∧A D. A∧(D∨?C)∧B 5. 在下列关于计算机语言的说法中,不正确的是()。 A. Pascal和C都是编译执行的高级语言 B. 高级语言程序比汇编语言程序更容易从一种计算机移植到另一种计算机上 C. C++是历史上的第一个支持面向对象的计算机语言 D. 与汇编语言相比,高级语言程序更容易阅读 6.某个车站呈狭长形,宽度只能容下一台车,并且只有一个出入口。已知某时刻该车站状态为空,从这一时刻开始的出入记录为:“进,出,进,进,进,出,出,进,进,进,出,出”。假设车辆入站的顺序为1,2,3,……,则车辆出站的顺序为()。 A. 1, 2, 3, 4, 5 B. 1, 2, 4, 5, 7 C. 1, 4, 3, 7, 6 D. 1, 4, 3, 7, 2 7.在C语言中,判断a不等于0且b不等于0的正确的条件表达式是() A. !a==0 || !b==0 B. !((a==0)&&(b==0)) C. !(a==0&&b==0) D. a && b 8.(2010)16 + (32)8的结果是()。 A. (8234)10 B. (202B)16 C. (20056)8 D. (100000000110)2 9.在C程序中,表达式200|10的值是() A. 20 B. 1 C. 220 D. 202 10.在下列各项中,只有()不是计算机存储容量的常用单位。 A. Byte B. KB C.UB D.TB 11.LAN 的含义是()。 A. 因特网 B. 局域网 C.广域网 D.城域网 12.以下断电之后仍能保存数据的有()。 A. 硬盘 B. 高速缓存 C. 显存 D. RAM

信息学奥赛培训计划(复赛)

信息技术学科信息学奥赛社团培训计划 制定人:玄王伟 2018年10月

信息学奥赛培训计划方案推进信息技术教育是全面实施素质教育的需要,是培养具有创新精神和实践能力的新型人才的需要。信息学奥赛的宗旨为:“丰富学生课余生活,提高学生学习兴趣,激发学生创新精神。”为此,我们应以竞赛作为契机进而培养学生综合分析问题、解决问题的意识和技能。 为响应学校号召,积极参与信息技术奥林匹克竞赛,校本课程特别开设C++语言程序设计部分,利用社团活动时间对部分学生进行辅导。教学材料以信息学奥赛一本通训练指导教程为主,力图让学生们对编写程序有较深入了解的同时,能够独立编写解决实际问题的算法,逐步形成解题的思维模式。因学习内容相对中小学学生具有一定的难度,本课程采用讲练结合的形式,紧紧围绕“程序=算法+数据结构”这一核思想,以数学问题激发学生学习兴趣,进而达到学习目标。为更好地保证信息学奥赛的培训效果,特制订本培训计划。 一、培训目标 1.使学生具备参加全国信息学奥林匹克竞赛分区联赛NOIP(初赛、复赛)的能力。 2.使学生养成较好的抽象逻辑推理能力、严谨的思维方式和严密的组织能力,并使学生的综合素质的提高。 3.使学生初步具备分析问题和设计算法的能力。 二、培训对象 我校小学及初中对信息学感兴趣且初赛成绩较好的学生,人数共

计14人,其中小学组12人,普及组2人。 三、培训要求 严格培训纪律,加强学生管理;信息学社团的组建由学生自愿报名、教师考察确定;培训过程中做与培训无关的事如打游戏、上网聊天等,一经发现作未参加培训处理;规定的作业、练习必须按时保质保量完成,否则按未参加培训处理。 四、培训内容 1.深入学习计算机基础知识,包括计算机软硬件系统、网络操作、信息安全等相关知识内容,结合生活实际让学生真正体会到参加信息学奥赛的乐趣。 2.全面学习C++语言的基础知识、学会程序的常用调试手段和技巧,在用C++解决问题的过程中引入基础算法的运用。 3.深入学习各类基础算法,让学生真正理解算法的精髓,遵循“算法+数据结构=程序”的程序设计思想,在算法设计的教学实例中引入数据结构的学习,从而形成一定的分析和解决问题的能力。 4.以实例为基础,展开强化训练,使学生开始具备运用计算机独立解决实际问题的能力。用计算机解决现实问题的最重要的一个前提就是数据模型的建立和数据结构的设计。数据模型的建立、数学公式的应用,是计算机解决问题的关键。因此,加强与数学学科的横向联系非常必要。 五、培训时间 自2018年10月份第三周开始至2018年11月中旬结束,包括每

关于堆栈和指针(指针例子解释很好)

关于堆栈和指针 堆栈是一种执行“后进先出”算法的数据结构。 设想有一个直径不大、一端开口一端封闭的竹筒。有若干个写有编号的小球,小球的直径比竹筒的直径略小。现在把不同编号的小球放到竹筒里面,可以发现一种规律:先放进去的小球只能后拿出来,反之,后放进去的小球能够先拿出来。所以“先进后出”就是这种结构的特点。 堆栈就是这样一种数据结构。它是在内存中开辟一个存储区域,数据一个一个顺序地存入(也就是“压入——push”)这个区域之中。有一个地址指针总指向最后一个压入堆栈的数据所在的数据单元,存放这个地址指针的寄存器就叫做堆栈指示器。开始放入数据的单元叫做“栈底”。数据一个一个地存入,这个过程叫做“压栈”。在压栈的过程中,每有一个数据压入堆栈,就放在和前一个单元相连的后面一个单元中,堆栈指示器中的地址自动加1。读取这些数据时,按照堆栈指示器中的地址读取数据,堆栈指示器中的地址数自动减1。这个过程叫做“弹出pop”。如此就实现了后进先出的原则。 堆栈是计算机中最常用的一种数据结构,比如函数的调用在计算机中是用堆栈实现的。 堆栈可以用数组存储,也可以用以后会介绍的链表存储。 下面是一个堆栈的结构体定义,包括一个栈顶指针,一个数据项数组。栈顶指针最开始指向-1,然后存入数据时,栈顶指针加1,取出数据后,栈顶指针减1。 #define MAX_SIZE 100 typedef int DATA_TYPE; struct stack { DATA_TYPE data[MAX_SIZE]; int top; }; 堆栈是系统使用是临时存储区域。它是后进先出的数据结构。 C++主要将堆栈用于函数调用。当函数调用时,各种数据被推入堆栈顶部;函数终止后的返回地址、传递给函数的参数、函数返回的结果以及函数中声明的局部变量等等。因此当函数A调用函数B调用函数C,堆栈是增长了,但调用完成后,堆栈又缩小了。 堆是一种长期的存储区域。程序用C++的new操作符分配堆。对new的调用分配所需的内存并返回指向内存的指针。与堆栈不同,你必须通过调用new明确的分配堆内存。你也必须通过调用C++的delete 操作符明确的释放内存,堆不会自动释放内存。 如果C++中的一个类是定义在堆栈上的,就使用"."开访问它的成员。如果是定义在堆上的,就使用"->"指针来开访问。但在,"->"操作符也可以用在堆栈上的类。 什么是指针? 和其它变量一样,指针是基本的变量,所不同的是指针包含一个实际的数据,该数据代表一个可以找到实

单链表代码

//lnkList.h 无头结点的单链表的类(lnkLIST类) #ifndef _lnkLIST_H_ #define _lnkLIST_H_ template class Link { //单链表的结点类型 public: T data; // 用于保存结点元素的内容 Link *next; // 指向后继结点的指针 Link(const T info, Link* nextValue = NULL) { //具有两个参数的Link构造函数data = info; next = nextValue; } Link(const Link* nextValue) { //具有一个参数的Link构造函数 next = nextValue; } }; templateclass lnkLIST {//带模板并继承lnkList的顺序表类 private: Link *head, *tail; // 单链表的头、尾指针 Link *setPos(const int i) // 返回线性表指向第i个元素的指针值 { if(i<1) return NULL; //当链表中结点数小于i时返回NULL int count = 1; Link *p = head; while (p != NULL && count < i) {// 循链定位 p = p-> next; count++; }; return p; // 指向第i 结点,i=0,1,…,} public: lnkLIST() // 构造函数 { head=NULL; tail=NULL; } ~lnkLIST() // 析构函数 {clear();} bool isEmpty() // 判断链表是否为空 { if(head==NULL) return 1; return 0; }

栈和队列

栈和队列 一、单项选择题(共59题) 1. 假定一个链式队列的队首和队尾指针分别用front和rear表示,每个结点的结构为: ,当出列时所进行的指针操作为() A. front = front->next; B. rear = rear->next; C. front->next = rear; rear = rear->next; D. front = front->next; front->next = rear; 答案:A 2. 向一个栈顶指针为HS的链栈中插入一个s所指结点时,则执行()。 A. HS->next = s; B. s->next = HS->next; HS->next = s; C. s->next = HS; HS = s; D. s->next = HS; HS = HS->next; 答案:C 3. 假定一个带头结点的循环链式队列的队首和队尾指针分别用front和rear表示,则判断队空的条件为()。 A. front == rear >next B. rear == NULL C. front == NULL D. front == rear 答案:D 4. 若让元素1, 2, 3, 4依次进栈,则出栈次序不可能出现()的情况。 A. 3, 2, 1, 4 B. 2, 1, 4, 3 C. 4, 3, 2, 1 D. 1, 4, 2, 3 答案:D 5. 假定一个顺序循环队列存储于数组a[N]中,其队首和队尾指针分别用f和r表示,则判断队满的条件为()。 A. (r - 1) % N == f B. (r + l) % N == f C. (f - 1) % N == r D. (f + l) % N == r 答案:B 6. 假定利用数组a[N]循环顺序存储一个队列,用f和r分别表示队首和队尾指针,并已知

最新中小学信息学竞赛活动开展工作总结

中小学信息学竞赛活动开展工作总结 中小学信息学竞赛活动开展工作总结 今年10月下旬,局领导明确中小学生的信息学竞赛由我站负责。我们当时觉得接受这个任务压力重大,这是因为我区的这一块工作与其他县(市、区)相比,差距较大,而且离开明年市赛只有四个多月的时间。当时的情况是邱隘中心小学有一定基础,华泰小学刚刚起步,其余小学都没有开展,就连前几年在这方面开展相对较好的咸祥镇中心小学也正处在停顿状态。我们设想如果经过100分的努力,也只能是刚刚接近三等奖,这在明年竞赛中还是反映不出成绩来。针对上述情况,我们确定了小学突破、初中紧跟的工作措施。具体小结如下: 一、小学生竞赛辅导起动快,成效显著。 1:统一认识、落实措施 我们迅速分别召开了愿意加入本项活动的小学正职校长及负责教学的校级领导会议。会上大家统一了认识,树立了信心,校长们表示一定会按排好工作,落实好切实可行的措施。 2:师生同学、共同进步

我区小学信息学老师多数是中师毕业,在校没有系统学过PASCAL 语言,带学生参加竞赛有较大难度,如果按常规先办教师培训班,学成后再去辅导学生,至少是一年以后的事情了。为了早出成绩,我们采取了师生同学的办法,教师现学现教,一边教一边学。自1月3日将举行***区小学生信息学竞赛,想利用这次比赛,进一步提高我区小学生信息学竞赛水平,赛后还将全区前30名学生集中起来,举办冬令营。 二、初中生竞赛工作方向确定,措施落实。 1:组织比武,了解师能 为了解掌握我区初中信息学教师的知识水平和教学能力,经教育局同意,组织了初中信息学教师信息学竞赛辅导水平比武活动,比武分初赛和复赛(初赛为笔试,笔试成绩不理想),月底将评出一、二、三等奖。 2:确定训点,强力推动 在了解掌握初中信息学教师师能的基础上,并给合小学竞赛活动开展情况,确定初中信息学竞赛培训点,同时出台相关政策,推

C语言中指针、数组和引用例子实例

一、指针:内容是指示一个内存地址的变量;类型是指示编译器怎么解释指针内容指向地址中的内容,以及该内存区域有多大; 例子: [cpp] int i = 0; int * pi = &i; printf(“pi = %x \n”, pi); // 打印pi的内容: 0x2000 printf(“*pi= %d \n” , *pi); // 打印pi指向地址中的值: 5 printf(“&pi= %x \n”, &pi); // 打印pi的地址: 0x100 从汇编的角度来看,指针是这样的: int i = 0; 010E139E mov dword ptr [i],0 int * pi = &i; 010E13A5 lea eax,[i] 010E13A8 mov dword ptr [pi],eax 二、数组:是一个单一数据类型对象的集合。其中单个对象没有被命名,通过索引访问。 数组名和指针的区别:数组名的内涵在于其指代实体是一种数据结构,这种数据结构就是数组。数组名的外延在于其可以转换为指向其指代实体的指针,而且是一个指针常量。指向数组的指针则是另外一种变量类型,仅仅意味着数组的存放地址 注意:虽然数组名可以转换为指向其指代实体的指针,但是它只能被看作一个指针常量,不能被修改,如下:天骄无双:https://www.doczj.com/doc/e416568725.html, [cpp] int intArray[10]; intArray++; // 错误 “指针和数组等价”说的是什么?索引操作相同,例如:p[2]; a[2]; 三、引用(reference)是一个对象的别名。用对象初始化引用后,对象的名字和引用都指向该对象; 引用是如何实现的?从汇编语言的角度来看,指针和引用是一样的: [cpp] int i = 0; 00E9139E mov dword ptr [i],0 int & ref = i; 00E913A5 lea eax,[i] 00E913A8 mov dword ptr [ref],eax int * pi = &i; 00E913AB lea eax,[i] 00E913AE mov dword ptr [pi],eax 指针和引用的区别(从C++使用角度来看): 不存在空引用 引用要初始化 引用初始化后,不能指向另一个对象 这是由编译阶段保证的。 备注:一个指向非常量的引用不能用字面值或者临时值初始化;但是一个指向常量的引用可以。天骄无双:https://www.doczj.com/doc/e416568725.html,

指针测试题

C++测试(指针) 学号姓名成绩 一、选择题(每题1.5分,共24分) 1.语句int a=10,*point=&a;其值不为地址。 A. point B. &a C. &point D. *point 2.若p为指针变量,y为变量,则y = *p++;的含义是 A.y=*p;p++ B.y=(*p)++ C.y=p;p++ D.p++;y=*p 3.语句char str[]=?visual C++?;char *p=str;则p的值为 A. ?visual C++? B.str的首地址 C. \n D.?v? 4.设有说明语句char *s[]={?student?,?Teacher?,?Father?,?Month?}, *ps=s[2];执行语句:cout<<*s[1]<<’,’<next=&b; D.(*p).next=q; 9.下面正确的语句是 A. int a[3][4],(*p)[4]; p=a; B. int a[3][4],*p[4]; p=a; C. int a[3][4],*p; p=a; D. int a[3][4],**p;*p=a; 10.下面不正确的语句是 A.float *p;p=new float[3]; B. int *p;p=new int[3](1,2,3); C. float *p;p=new float(3); D. int (*p)[4];p=new int[3][4]; 11.设有函数定义:int f1(void){return 100,150;}调用函数f1()时, A.函数返回值100 B. 函数返回值150 C. 函数返回二个值100和150 D. 语句return 100,150;语法错. 12.设有语句:int fun(char *,int &);char str[100];int k;则对函数fun的正确的调用形式是 A.fun(str,&k) B.fun(str,k) C.fun(str[100],k) D.fun(str, &k) 13.数组作为函数的形参时,把数组名作为实参,传递给函数的是 A.该数组的首地址 B. 该数组的元素个数 C. 该数组中的各元素值 D. 该数组的大小 14.执行以下语句序列:则 enum {Sun,Mon,Tue,Wed,Thu,Fri,Sat}c1,c2; //A

中小学信息学程序设计竞赛细则

中小学信息学程序设计竞赛细则 一、竞赛组织 1.由武汉市中小学信息技术创新与实践活动组委会负责全市的竞赛组织工作,竞赛由全市统一命题,各区按全市统一要求负责考务工作。 2.活动分为二个阶段,第一阶段为初赛阶段,竞赛以笔试闭卷形式,按小学组、初中组和高中组三个学段同时进行,由各区具体负责实施。第二阶段为复赛阶段,竞赛以上机形式,按小学组、初中组和高中组三个学段进行。复赛由市统一命题,统一安排考场,地点待定。 二、竞赛的报名和办法 1.报名费每生20元。 2.竞赛报名以区为单位,统一组织学生报名。 3.3月20日(星期五)前各区、系统集中到市教科院信息技术教育中心(6012室)报名,过时不再补报。 4.各区、系统向市报名时,只需按组别和语种、各校报名人数、指导教师姓名等要求填好的初赛报名表,以及缴纳相应的报名费,无须交具体参赛名单。初赛报名表如下: 三、竞赛日期和时间 1.初赛时间:待定 2.复赛时间:待定 四、竞赛形式及试题类型 小学组(LOGO或BASIC)中学组(C或PASCAL) 复赛:全卷满分100分,考试时间小学80分钟、中学120分钟。中学采用的程序设计语言:C和PASCAL。小学采用的程序设计语言:LOGO或BASIC。 竞赛分组:小学组,BASIC、LOGO任选。中学分初中组和高中组,C、PASCAL任选。

附件:武汉市青少年信息学(计算机)奥林匹克竞赛内容及要求: A、小学组 一、初赛内容与要求 1.计算机的基本知识 ★诞生与发展★特点★计算机网络、病毒等基本常识 ★在现代社会中的应用★计算机的基本组成及其相互联系 ★计算机软件知识★计算机中的数的表示 2.计算机的基本操作 ★MS—DOS与Windos98操作系统使用基础知识(启动、命令格式、常用格式) ★常用输入/输出设备的种类、功能、特性、使用和维护 ★汉字输入/输出方法和设备★常用计算机屏幕信息 3.程序设计基本知识 (1)程序的表示 ★自然语言的描述★QBASIC和LOGO4. 0语言描述 (2)数据结构的类型 ★简单数据的类型;整型、实型、字符型 ★构造类型;数组、字符串 (3)程序设计 ★结构化程序设计的基本概念★阅读程序的能力 ★具有完成下列过程的能力 现实世界(问题):指知识范畴的问题—信息世界(表述解法)—计算机世界(将解法用计算机能够实现的数据结构和算法述出来) (4)基本算法处理 ★字串处理★排序★查找 二、复赛内容与要求 在初赛的内容上增加以下一些内容: (1)计算机软件: ★操作系统的基本知识 (2)程序设计: ★设计测试数据的能力★编写文档资料的能力 (3)算法处理 ★简单搜索★统计★分类★递归算法 三、有关分组内容及难度的说明 (1)LOGO语言 A.熟练掌握尾归和多层递归,对中间递归有一定的了解,熟练掌握字表处理基本命令。 B.掌握取整、随机、随机化、求商取整、求商取余函数的使用方法。 (2)BASIC语言 A.BASIC语言的一维数组:正确定义一个数组,掌握数组中各元素间的相互关系,熟练掌握对数组中各元素的赋值和引用,其中包括对数组所进行的几种基本处理,如选数列中最大、最小数,对有序数列的插入,对数列进行排序、查找等。 B.BASIC语言的函数:熟练地掌握数值函数的运用(如取整函数、随机函数、绝对值函数等)。 B、中学组

数据结构第3章 栈与队列习题

第3章栈与队列 一、单项选择题 1.元素A、B、C、D依次进顺序栈后,栈顶元素是,栈底元素是。 A.A B.B C.C D.D 2.经过以下栈运算后,x的值是。 InitStack(s);Push(s,a);Push(s,b);Pop(s,x);GetTop(s,x); A.a B.b C.1 D.0 3.已知一个栈的进栈序列是ABC,出栈序列为CBA,经过的栈操作是。 A.push,pop,push,pop,push,pop B.push,push,push,pop,pop,pop C.push,push,pop,pop,push,pop D.push,pop,push,push,pop,pop 4.设一个栈的输入序列为A、B、C、D,则借助一个栈所得到的序列是。 A.A,B,C,D B.D,C,B,A C.A,C,D,B D.D,A,B,C 5.一个栈的进栈序列是a,b,c,d,e,则栈的不可能的输出序列是。 A.edcba B.decba C.dceab D.abcde 6.已知一个栈的进栈序列是1,2,3,……,n,其输出序列的第一个元素是i,则第j个出栈元素是。 A.i B.n-i C.j-i+1 D.不确定 7.已知一个栈的进栈序列是1,2,3,……,n,其输出序列是p1,p2,…,Pn,若p1=n,则pi的值。 A.i B.n-i C.n-i+1 D.不确定 8.设n个元素进栈序列是1,2,3,……,n,其输出序列是p1,p2,…,p n,若p1=3,则p2的值。 A.一定是2 B.一定是1

C.不可能是1 D.以上都不对 9.设n个元素进栈序列是p1,p2,…,p n,其输出序列是1,2,3,……,n,若p3=1,则p1的值。 A.可能是2 B.一定是1 C.不可能是2 D.不可能是3 10.设n个元素进栈序列是p1,p2,…,p n,其输出序列是1,2,3,……,n,若p3=3,则p1的值。 A.可能是2 B.一定是2 C.不可能是1 D.一定是1 11.设n个元素进栈序列是p1,p2,…,p n,其输出序列是1,2,3,……,n,若p n=1,则p i(1≤i≤n-1)的值。 A.n-i+1 B.n-i C.i D.有多种可能 12.判定一个顺序栈S为空的条件为。 A.S.top= =S.base B.S.top!= S.base C.S.top!= S.base+S.stacksize D.S.top= = S.base+S.stacksize 13.判定一个顺序栈S为栈满的条件是。 A.S.top-S.base= =S.stacksize B.S.top= = S.base C.S.top-S.base!=S.stacksize D.S.top!= S.base 14.链栈与顺序栈相比有一个明显的优点,即。 A.插入操作方便B.通常不会出现栈满的情况 C.不会出现栈空的情况D.删除操作更加方便 15.最不适合用作链栈的链表是。 A.只有表头指针没有表尾指针的循环双链表 B.只有表尾指针没有表头指针的循环双链表 C.只有表尾指针没有表头指针的循环单链表 D.只有表头指针没有表尾指针的循环单链表 16.如果以链表作为栈的存储结构,则退链栈操作时。 A.必须判别链栈是否满B.判别链栈元素的类型 C.必须判别链栈是否空D.对链栈不作任何判别

2019-2020年中学生信息学奥林匹克初赛模拟试题附参考答案

2019-2020 年中学生信息学奥林匹克初赛模拟试题附参考答案 一、选择题(共20题,每题 1.5 分,共计30分。前10 题为单选题;后10题为不定项选择题) 1. 微型计算机的性能主要取决于( )。 A)内存B)主板C)中央处理器D)硬盘 E )显示器 2. 128KB 的存储器用十六进制表示,它的最大的地址码是( ) A)10000 B)EFFF C)1FFFF D)FFFFF E)FFFF 3. 能将高级语言程序转换为目标程序的是( ). A)调试程序B) 解释程序C) 编辑程序D) 编译程序E) 连接程序 4.A=11001010B,B=00001111B,C=01011100B,则A∨B∧C=( )B A)01011110 B)00001111 C)01011100 D)11001110 E)11001010 5. 计算机病毒传染的必要条件是( ) 。 A) 在内存中运行病毒程序B) 对磁盘进行读写操作 C) 在内存中运行含有病毒的可执行程序D) 复制文件E) 删除文件 6. TCP /IP 协议共有( ) 层协议 A)3 B)4 C)5 D)6 E)7 7.192.168.0.1 是属于( ). A)A 类地址B)B 类地址C)C 类地址D)D 类地址E)E 类地址 8. 对给定的整数序列(54,73,21,35,67,78,63,24,89) 进行从小到大的排序时, 采用快速排序的第一趟扫描的结果是( ). A)(24,21,35,54,67, 78,63,73,89) B)(24,35,21,54,67, 78,63,73,89) C) (24,21,35,54,67, 63,73,78,89) D)(21,24,35,54,63, 67,73,78,89) E)(24,21,35,54,67, 63,73,78,89) 9. 一棵n 个结点的完全二叉树, 则二叉树的高度h 为( ). n log 2 n A) B) log 2 n C) 2D) log 2 n 1 E)2n-1 22 10. 对右图进行广度优先拓扑排序得到的顶点序列正确的是( ). A)1,2,3,4,5,6 B)1,3,2,4,5,6 C)1,3,2,4,6,5 D) 1,2,3,4,6,5 E)1,3,2,4,5,6 11. 下列属于冯.诺依曼计算机模型的核心思想是( ). A) 采用二进制表示数据和指令B) 采用“存储程序”工作方式

(完整)信息学奥赛(NOIP)必看经典书目汇总,推荐文档

信息学奥赛(NOIP)必看经典书目汇总! 小编整理汇总了一下大神们极力推荐的复习资料!(欢迎大家查漏补缺) 基础篇 1、《全国青少年信息学奥林匹克分区联赛初赛培训教材》(推荐指数:4颗星) 曹文,吴涛编著,知识点大杂烩,部分内容由学生撰写,但是对初赛知识点的覆盖还是做得相当不错的。语言是pascal的。 2、谭浩强老先生写的《C语言程序设计(第三版)》(推荐指数:5颗星) 针对零基础学C语言的筒子,这本书是必推的。 3、《骗分导论》(推荐指数:5颗星) 参加NOIP必看之经典 4、《全国信息学奥林匹克联赛培训教程(一)》(推荐指数:5颗星) 传说中的黄书。吴文虎,王建德著,系统地介绍了计算机的基础知识和利用Pascal语言进行程序设计的方法 5、《全国青少年信息学奥林匹克联赛模拟训练试卷精选》 王建德著,传说中的红书。 6、《算法竞赛入门经典》(推荐指数:5颗星) 刘汝佳著,算法必看经典。 7、《算法竞赛入门经典:训练指南》(推荐指数:5颗星) 刘汝佳著,《算法竞赛入门经典》的重要补充 提高篇 1、《算法导论》(推荐指数:5颗星) 这是OI学习的必备教材。

2、《算法艺术与信息学竞赛》(推荐指数:5颗星) 刘汝佳著,传说中的黑书。 3、《学习指导》(推荐指数:5颗星) 刘汝佳著,《算法艺术与信息学竞赛》的辅导书。(PS:仅可在网上搜到,格式为PDF)。 4、《奥赛经典》(推荐指数:5颗星) 有难度,但是很厚重。 5、《2016版高中信息学竞赛历年真题解析红宝书》(推荐指数:5颗星) 历年真题,这是绝对不能遗失的存在。必须要做! 三、各种在线题库 1、题库方面首推USACO(美国的赛题),usaco写完了一等基本上就没有问题,如果悟性好的话甚至能在NOI取得不错的成绩. 2、除此之外Vijos也是一个不错的题库,有很多中文题. 3、国内广受NOIP级别选手喜欢的国内OJ(Tyvj、CodeVs、洛谷、RQNOJ) 4、BJOZ拥有上千道省选级别及以上的题目资源,但有一部分题目需要购买权限才能访问。 5、UOZ 举办NOIP难度的UER和省选难度的UR。赛题质量极高,命题人大多为现役集训队选手。

C语言数组指针的小例子

1、功能:输入6个学生的5门课程成绩,计算出每个学生的平均分和每门课程的平均分。 2、C语言实现代码:(其实就是用二维数组来实现的,二维数组的引用传递使用数组指针来完成) 复制代码代码如下: #include <stdio.h> #define STUDENT 5 #define SCORE 6 void input_array(float (*score)[STUDENT]); void avg_score(float (*score)[STUDENT]); void avg_course(float (*score)[STUDENT]); /** * calculate student average score and course average socore. */ int main(){ float a[SCORE][STUDENT]; input_array(a); avg_course(a); avg_score(a); } void input_array(float (*score)[STUDENT]){ int i, j; for(i=0; i<SCORE; i++){ printf("input the %d student score:", i+1); for(j=0; j<STUDENT; j++){ scanf("%f", score[i] + j); } } } void avg_course(float (*score)[STUDENT]){ int i,j; float s; for(j=0; j<STUDENT; j++){ printf("course%d ", j); } printf("n"); for(i=0; i<SCORE; i++){ s=0; for(j=0; j<STUDENT; j++){ printf("%f ", *(score[i] + j)); s += *(score[i] + j); }

实现单链表的各种基本运算

实现单链表的各种基本运算 一、实验目的 了解单链表表的结构特点及有关概念,掌握单链表的各种基本操作算法思想及其实现。 二、实验内容 编写一个程序,实现顺序表的各种基本运算: 1、初始化单链表; 2、单链表的插入; 3、单链表的输出; 4、求单链表的长度 5、判断单链表是否为空; 6、输出单链表的第i位置的元素; 7、在单链表中查找一个给定元素在表中的位置; 8、单链表的删除; 9、释放单链表 三、算法思想与算法描述简图

主函数main void InitList(LinkList*&L) 初始化单链表L void DestroyList(LinkList*&L)//释放单链表L int ListEmpty(LinkList*L)//判断单链表L是否为空集 int Listlength(LinkList*L)//返回单链表L的元素个数 void DispList(LinkListt*L)//输出单链表L int GetElem(LinkList*L,int i,char e)/*ElemType e)获 取单链表L中的第i个元素*/ int LocateEmpty(LinkList*L,char e)/*ElemType e)在单 链表L中查找元素e*/ int ListInsert(LinkList*&L,int i,char e)/*ElemType e) 在单链表中第i个位置上插入元素e*/ int ListDelete(LinkList*&L,int i,char &e)/*ElemType e)在单链表L中删除第i个元素*/

四、实验步骤与算法实现 #include #include typedef char ElemType; typedef struct LNode//定义单链表 { ElemType data; struct LNode *next; }LinkList; void InitList(LinkList*&L) { L=(LinkList*)malloc(sizeof(LinkList));//创建头结点 L->next=NULL;//头结点赋值为空 } void DestroyList(LinkList*&L)//销毁单链表(释放单链表L占用的内存空间即逐一释放全部结点的空间) { LinkList*p=L,*q=p->next; while(q!=NULL) {free(p); p=q; q=p->next;} free(p); } int ListEmpty(LinkList*L)//判线性表是否为空表ListEmpty(L) { return(L->next==NULL);}//若单链表L没有数据结点,则返回真,否则返回假。 int ListLength(LinkList*L)//求线性表的长度ListLength(L) { LinkList*p=L;int i=0; while(p->next!=NULL)

NOIP2016信息学奥赛普及组初赛C++试题及参考答案 较完美版

精心整理 NOIP2016第二十二届全国青少年信息学奥林匹克联赛初赛 普及组C++语言试题 竞赛时间:2016年10月22日14:30~16:30 一、单项选择题(共20题,每题1.5分,共计30分;每题有且仅有一个正确选项) 1.以下不是微软公司出品的软件是()。 A .Powerpoint B .WordC.ExcelD.AcrobatReader 2.如果256种颜色用二进制编码来表示,至少需要()位。 A .6 B .7 C .8 D .9 3.以下不属于无线通信技术的是()。 A .蓝牙45A .光盘6A 、字母键S 出的第A .A B .78A .0.8B 9A C 10A C 11标为()。 A.6B .10C .12D .15 12.若有如下程序段,其中s 、a 、b 、c 均己定义为整型变量,且a 、c 均己赋值(c 大于0)。 s=a; for(b=1;b<=c;b++) s=s+1; 则与上述程序段修改s 值的功能等价的赋值语句是()。 A.s=a+b; B.s=a+c; C.s=s+c; D.s=b+c; 13.有以下程序: #include usingnamespacestd; intmain(){

intk=4,n=0; while(n

信息学奥赛数据结构教程PASCAL版

信息学奥赛数据结构教程PASCAL版第二课堆栈和队列 一、堆栈 1(概述 栈(stack)是一种特殊的线性表。作为一个简单的例子,可以把食堂里冼净的一摞碗看作一个栈。在通常情况下,最先冼净的碗总是放在最底下,后冼净的碗总是摞在最顶上。而在使用时,却是从顶上拿取,也就是说,后冼的先取用,后摞上的先取用。好果我们把冼净的碗“摞上”称为进栈,把“取用碗”称为出栈,那么,上例的特点是:后进栈的先出栈。然而,摞起来的碗实际上是一个表,只不过“进栈”和“出栈”,或者说,元素的插入和删除是在表的一端进行而已。 一般而言,栈是一个线性表,其所有的插入和删除均是限定在表的一端进行,允许插入和删除的一端称栈顶(Top),不允许插入和删除的一端称栈底(Bottom)。若给定一个栈S=(a1, a2,a3,…,an),则称a1为栈底元素,an为栈顶元素,元素ai位于元素ai-1之上。栈中元素按a1, a2,a3,…,an 的次序进栈,如果从这个栈中取出所有的元素,则出栈次序为an, an-1,…,a1 。也就是说,栈中元素的进出是按后进先出的原则进行,这是栈结构的重要特征。因此栈又称为后进先出(LIFO—Last In First Out)表。我们常用一个图来形象地表示栈,其形式如下图:

通常,对栈进行的运算主要有以下几种: (1) 往栈顶加入一个新元素,称进栈; (2) 删除栈顶元素,称退栈; (3) 查看当前的栈顶元素,称读栈。 此外,在使用栈之前,首先需要建立一个空栈,称建栈;在使用栈的过程中, 还要不断测试栈是否为空或已满,称为测试栈。 2(栈的存储结构 栈是一种线性表,在计算机中用向量作为栈的存储结构最为简单。因此,当用编程语言写程序时,用一维数组来建栈十分方便。例如,设一维数组STACK[1..n] 表示一个栈,其中n为栈的容量,即可存放元素的最大个数。栈的第一个元素,或称栈底元素,是存放在STACK[1]处,第二个元素存放在STACK[2]处,第i个元素存放在STACK[i]处。另外,由于栈顶元素经常变动,需要设置一个指针变量top,用来指示栈顶当前位置,栈中没有元素即栈空时,令top=0,当top=n时,表示栈满。 3(对栈的几种运算的实现方法: (1)建栈 continue to respond 5min. Remove the absorption tube, 1cm Cuvette, wavelength of 400nm, to standard pipes zero regulating and absorbs

c语言指针GetMemory经典例子

GetMemory的典型例子 2010-01-13 18:24 520人阅读评论(2) 收藏举报//NO.1:程序首先申请一个char类型的指针str,并把str指向NULL(即str里存的是NULL的地址,*str为NULL中的值为0),调用函数的过程中做了如下动作:1申请一个char 类型的指针p,2把str的内容copy到了p里(这是参数传递过程中系统所做的),3为p指针申请了100个空间,4返回Test函数.最后程序把字符串hello world拷贝到str 指向的内存空间里.到这里错误出现了!str的空间始终为NULL而并没有实际的空间.深刻理解函数调用的第2步,将不难发现问题所在! void GetMemory(char *p) { p = (char*)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); } //请问运行Test函数后会是什么样的结果? //NO.2:程序首先申请一个char类型的指针str,并把str指向NULL.调用函数的过程中做了如下动作:1申请一数组p[]并将其赋值为hello world(数组的空间大小为12),2返回数组名p付给str指针(即返回了数组的首地址).那么这样就可以打印出字符串"hello world"了么?当然是不能的!因为在函数调用的时候漏掉了最后一步.也就是在第2步return数组名后,函数调用还要进行一步操作,也就是释放内存空间.当一个函数被调用结束后它会释放掉它里面所有的变量所占用的空间.所以数组空间被释放掉了,也就是说str所指向的内容将不确定是什么东西. char *GetMemory(void) { char p[] = "hello world"; return p;

相关主题
文本预览
相关文档 最新文档