C基础—指针/指针与动态变量/指针与数组/指针与函数2011-08-30 21:39:41| 分类: MCU | 标签: |字号大中小 订阅 三、C/C++基础——指针 1. 指针基本性质 l 指向变量的指针类型通常与该类型的指针变量同时定义,其格式为; <类型> *<指针变量>; 其中,<类型>可以是任意的C/C++数据类型;<指针变量>为所定义的指针类型变量的名字,用标识符表示;符号“*”表示定义的是指针变量,以区别于普通变量定义。如定义一个可以指向int型变量的指针类型变量p:int *p; l 指针变量拥有自己的内存空间,在该空间中存储的是另一个变量的地址,即指针变量的值是另一个变量的内存地址。 l int *p, *q; //p和q均为指针变量 int *p, q; //p为指针变量,q为整形变量 为了避免在一个定义中定义(或声明)多个指针变量时的麻烦和可能带来的错误,可以先用typedef来给一个指针类型取一个名字,然后再用该指针类型来定义指针变量。如 typedef int* Pointer; //给int型指针类型取名Pointer Pointer p, q; //p和q均为指针类型变量 l 可以通过取地址符“&”来获得一个变量的地址。对于数组变量和函数,它们的内存首地址可以不用“&”操作来获得,数组变量名和函数名本身就表示它们在内存中的首地址。值得注意的是,每一个地址都属于栽一种指针类型,如: int x; double y; 则&x的类型为int *;而&y的类型为double *。 l 对指针类型的数据可以进行赋值、访问所指向的数据、加/减以及比较等运算。 l 对于字符指针变量,下面的操作表示把字符串的首地址赋值给它: char *q; q = “ABCD”; //q指向字符串“ABCD”在内存中的首地址。 l 任何类型的指针都可以赋给void *类型的指针变量,如: int x; double y; void *any_pointer; any_pointer = &x; //OK any_pointer = &y; //OK l 指针变量值的两种初始化方式: 1. int x, *p; p=&x; 2. int x; int *p=&x; l 对于一个指针类型变量,可以通过间接访问操作符“*”来访问它所指向的变量。 1. 如:int x; int *p = &x; 若想给x赋值1,可以通过x = 1或*p = 1来完成赋值。 l 对于一个未初始化的指针变量,如果访问它所指向的变量会产生严重的后果。 l 当一个指针与一个整形值进行加/减运算时,实际加/减的值由该指针所指向的变量类型来定。 l 指针的加/减运算通常用于以指针方式来访问数组元素。如下面的一维数组变量a: int a[10]; 除了用a[0],a[1],…,a[9]来访问数组a的元素外,还可以通过定义一个指针p,并让它指向数组a的第一个元素: int *p; p = &a[0]; 然后采用*p,*(p+1),…,*(p+9)的形式来访问数组a的元素。另外,对于一个指针变量,也可以采用访问数组元素的形式来访问它所指向的变量,其格式为: <指针变量>[<整形表达式>] 等价于 *(<指针变量>+<整形表达式>) 利用指针变量的这种用法,我们可以实现另外一种访问数组a的元素的形式:p[0],p[1],…,p[9]。对于指针变量的上述用法,在语法上并不要求p一定要指向一个数组,但通常只有当p指向一个数组时,这么用才有实际意义。 l 两个同类型指针的比较,其含义是比较它们所对应的内存地址的大小。
2. 指针作为形参类型 l 如果函数调用者需要从被调用的函数获得多个计算结果(函数的返回值机制只能返回一个结果),则可以在调用时,把调用者用于存储计算结果的变量的地址传给函数,函数中把计算结果通过间接方式赋给调用者提供的用于存储结果的变量。 l 在C/C++中,为了能在函数中改变实参的值,函数的形参应定义为指针类型,在函数调用时,把实参的地址传给函数,在函数中通过实参的地址间接地改变实参的值。 l 数组参数的黑夜传递方式是把实参数组的首地址传给函数;对于一些大型的结构体类型的数据,通常也会采用地址传递方式把它传给函数。如: struct A //定义一个结构体类型 { int no; //结构体类型成员 } void f(A *p) //定义一个指向结构体A这种数据类型的指针变量p { P—>no…… } int main { A a; f(&a); } l 在C/C++中,把指针作为形参的类型可以产生两个效果: 1. 提高参数传递效率 2. 通过形参改变实参的值 通常情况下,第一种效果用于大量数据的参数传递,而第二种效果则用于把函数的计算结果(有多个)通过参数返回给调用者。为了使得指针类型的参数传递只有上述(1)的效果而没有(2)的效果,可以把形参定义为指向常量的指针,使得函数无法通过指针类型的形参来改变实参的值。 l 指向常量的指针类型定义格式:const <类型> *<指针变量>; const的含义是不能改变<指针变量>所指向的数据的值。如下面定义了一个指向常量的指针类型的变量p: const int *p; const int x = 0; p = &x; //OK *p = 1; //Error 1. 虽然从形式上看,指向常量的指针变量只能指向常量,但从实际意义上讲,它可以指向变量,只不过不能通过它来改变所指向的变量的值而已。因此,对于上面定义的指向常量的指针变量p,也可以指向下面定义的变量y,但不能通过p改变y的值: int y; p = &y; //OK *p = 1; //Error y = 1; //OK 2. 对于一个指向变量的指针变量,不允许它指向一个常量,例如: const int x=0; int *p; p = &x; //Error 之所以不允许上面的p指向x,是因为下面的操作是合法的: *p = 1; 如果允许p指向常量,这将导致常量的值被修改。 3. 注意:不要把“指向常量的指针类型”与“指针类型的常量”混淆了!下面是一个指针常量: int x, y; int *const p = &x; //定义了一个指针类型的常量p,它指向一个变量。 *p = 1; //OK,*p是一个变量 p = &y; //Error,p是一个常量,其值不能被修改 上面定义的是一个指针类型的常量(必须要初始化),可以改变它所指向的变量x的值,但不能改变它本身的值。然而,对于下面定义的指针类型的常量p,既不能改变p的值,也不能p指向的值: int x, y; const int *const p = &x; *p = 1; //Error p = &y; //Error l 函数的返回值类型可以是一个指针类型。不过值得注意的是,不能把局部变量的地址作为指针返回给调用者,这是因为,当函数返回后,局部变量的内存空间已经被收回,如果调用者在使用这个内存空间中的值之前又调用了某个函数,则该空间将被新调用的函数所占用并拥有了新的值,这样调用者便得不到原来的值。下面的函数max返回一个一给数组中的最大元素的地址(指针): int *max(const int x[ ], int num) { int max_index = 0; for (int i = 1; i <num; i++) if (x[i] > x[max_index] ) max_index = I; return &x[max_index] } 利用函数max返回的指针,我们可以访问数组中的最大值。如: int a [10], *p; p = max (a, 10);
3. 指针与动态变量 l 在程序中往往需要处理这样的一些复合数据:构成复合数据的元素的个数在程序运行前无法 确定,必须在程序运行时,根据具体情况来确定。那在程序中如何表示和存储这些数据呢?C/C++是一种静态类型语言,程序中定义的每个变量的类型在编译时刻必须确定,变量的类型规定了变量的尺寸,因此用一般的变量是无法表示上面问题中的数据的。 在程序可以采用动态变量机制来解决上述问题。所谓动态变量是指:从静态的程序中无法确定它们的存在,只有当程序运行起来,随着程序的运行,它们根据程序的需要动态产生和消亡。虽然,局部变量也是动态产生和消亡,但在程序运行前,编译程序已经知道它们的存在,并且,局部变量一般是自动生成(如进入函数时)和自动消亡(退出函数时)。另外,局部变量的内存空间分配在程序的栈中,而动态变量的内存空间则是分配在程序的堆区中。 由于动态变量没有名字,因此,对动态变量的访问需要通过指向动态变量的指针变量来进行(间接访问)。 l 动态变量的创建: 1. new <类型名>:该操作在程序运行时在程序的堆区中产生一个类型由<类型名>指定的动态变量,结果为该动态变量的地址(或指针)。如下面操作产生一个int型的动态变量,该动态变量由指针p来指向: int *p; p = new int; //产生一个动态的整形变量,p指向该变量 *p = 1; //只能通过指针变量来访问该动态的整形变量 2. new <类型名>[<第1维的大小>]…[<第n维的大小>]:产生一个动态的n维数组,数组元素的类型由<类型名>表示。结果为数组的首地址,其类型由数组的维数来表示,如: int *p; p = new int[20]; //产生一个由20个整型元素所构成的一维动态数组 int (*q)[20]; //或者,typedef int A[20]; A *q; q = new int[10][20]; //产生一个由10*20个整型元素构成的二维动态数组。 3. void *malloc(unsigned int size):函数malloc在程序的堆区中分配一块大小为size的内存空间,并返回该内存空间的首地址,其类型为void *,注意,任何类型的指针都可以赋给void *类型的指针变量。如果该空间用于存储某个具体类型的数据,则需对返回值类型进行强制类型转换。如: int *p; double *q; typedef int A[20]; //定义一个由20个int型元素所构成的一给数组类型A A *r; //或,int (*r)[20] p = (int *)malloc(sizeof(int)); //创建一个整型动态变量 q = (double *)malloc(sizeof(double)*20); //创建一个一维动态数组变量 r = (A *)malloc(sizeof(int)*10*20); //创建一个二维动态数组变量 4. new与malloc的主要区别: (1)new自动计算所需分配的空间大小,而malloc则需要显式指出。 (2)new自动返回相应类型的指针,而malloc要做强制类型转换。 (3)如果创建的是动态对象,new会去调用相应对象类的构造函数而malloc不会。 5. 对于new和malloc,如果程序的堆区中没有足够的空间供分配,则返回空指针 NULL,或产生bad_alloc异常。 l 动态变量的撤消:动态变量不会自动消亡,在一个函数调用中创建的动态变量,函数返回后仍然存在(可以使用)。如果程序运行中不再需要某个动态变量了,则应该显式地使之消亡。一般情况下,用new产生的动态变量,用delete使之消亡;用malloc产生的动态变量,用free使之消亡。注意,不能使用delete和free撤消非动态变量,否则产生程序异常错误。格式如下: 1. delete<指针变量> 撤消<指针变量>所指向的动态变量。如: int *p = new int; ……………… delete p; //撤消p所指向的动态变量。 2. delete []<指针变量> 撤消<指针变量>所指向的动态数组变量(任何维数)。如: int *p = new int[20]; ……………… delete []p; //撤消p所指向的动态数组。 3. void free(void *p) 释放p所指向的内存空间。如 int *p = (int *)malloc(sizeof(int)); int *q = (int *)malloc(sizeof(int)*20); ……………… free(p); //撤消p所指向的动态变量 free(q); //撤消q所指向的动态数组 l 动态变量的应用—链表 在程序中常常需要处理由多个同类型的具有顺序关系的元素所构成的复合数据。如果在程序运行前能确定元素的个数,则可以采用数组来表示并存储这些数据;如果程序运行前无法确定元素的个数,元素个数需要到程序运行时才能确定,如何解决?一种方法是定义一个很大的数组,但这种方法的问题是:运行时,如果数据很少,则浪费空间;而如果数据很多以至于超出了预定数组的大小 ,则程序不能正常运行。另一种解决办法是用动态数组:如对输入的若干个数进行排序,输入时先输入数的个数,然而再输入数据;或者先输入各个数,最后输入一个结束标记。上述方法虽然可行,但是,当数组空间不够时,它需要重新申请空间,进行数据转移以及释放原有空间,这样程序效率不高。另外,用数组来表示一组具有顺序关系的元素所构成的数据,除了要考虑数组对元素个数的限制外,当在数组中增加或删除元素时,还将会面临数组元素的大量移动问题。采用一种称为链表的数据表示可以避免数组上述问题。 链表用于表示由若干个(个数不定)同类型的元素所构成的具有线性结构的复合数据。链表中的每一个元素除了本身的数据外,还包含一个(或多个)指针,它指向链表的下一个(和前一个)元素。如果每个元素只包含一个指针,则称为单链表,否则称为多链表。上述的定义意味着链表元素在内存空间中不必存放在连续的空间内。 如下单链表: [head]--->[a1 | next]--->..........--->[an | NULL] 该单链表由若干结点(元素)构成,每个结点除了具有存储结点数据的域以外,还包含一个指针域,它指向下一个结点,最后一个结点的指针域为空(NULL)。另外,一个单链表还需要有一个头指针(head),指向它的第一个结点。上述单链表中的结点类型和表头指针变量的定义如下: struct Node { int content; //代表结点的数据 Node *next; //一个结点的地址 }; //结点的类型定义 Node *head = NULL; //头指针变量定义,初始状态下为空值(表中没有结点) 对链表的操作包括:在链表中插入一个结点;在链表中删除某个结点;在链表中检索某个值。 4. 指针与数组 对数组元素的访问通常是通过下标来实现的,然而频繁地采用下标访问数组元素(如在循环中)有时效率不高,这是因为以下标方式访问数组元素时需要计算数组元素的地址。在计算数组元素地址时,首先要计算下标的值,然后根据数组的首地址和下标的值,计算出欲访问元素的内存地址。使用针对来访问数组元素,除了有时能提高程序效率外,它往往也体现了一种C/C++语言的特征。 l 以指针方式访问数组元素时,首先要获得数组元素的地址。对于一个一维数组,其首地址可以通过第一个元素获得: int a[10]; int *p = &a[0]; 除此之外,一维数组的首地址也可以通过数组变量名来获得,如: p = a; l 一维数组变量的名字表示第一个元素的内存地址,它是一个常量,程序中不能修改它。当把一个一维数组传给一个函数时,实际传递的是该一维数组的第一个元素的地址,如: void f(int *p, int num) //或void f(int p[], int num) { …….*(p+i)…… //或p[i],访问一维数组的第i个元素。 } int main() { int a[10]; f(a,10); //或f(&a[0], 10); } l 对于一个n(n>1)维数组,可以按一个一维数组理解它,该一维数组元素的个数为第一维的大小,元素的类型为去掉每一维后的n-1维数组。如: int b[20][10]; //二维数组 可以理解为: typedef int A[10]; //A表示一个由10个int型元素所构成的一维数组类型 A b[20]; //b为由20个A类型的元素所构成的一维数组变量
对于上面的数组变量b,其内存首地址可以用下面两种方式来获得: (1) b或&b[0],它表示变量b[0](数组第一行,类型为A)的地址,类型为A*。 (2) b[0]或&b[0][0],它表示变量b[0][0](数组第一行第一列,类型为int)的地址,基类型为int*。 虽然b(或&b[0])和b[0](或&b[0][0])所表示的内存地址相同,但它们属于不同 的指针类型。如: A *p; //或int (*p)[10] int *q; p = b; //或p = &b[0],p指向二维数组的第一行 p++; //p指向变量b[1],即二维数组的第二行 q = b[0]; //或q = &b[0][0],q指向二维数组第一行的第一个元素 q++; //q指向变量b[0][1],即二维数组第一行的第二个元素
当把一个二维数组传给一个函数时,实际传递的是该二维数组的第一行的地址。如: void g(int (*p)[10], int lines) //或void f(int p[ ][10], int lines) { …*(*(p+i)+j)… //或p[i][j],访问二维数组的第i行,第j列的元素 } int main() { int b[20][10]; g(b,20); //或g(&b[0], 20); } l 注意:int (*p)[10] 与 int *p[10] 是不同的。前者定义了一个指针变量p,它可以指向一个由10个int 元素组成的一维数组;后者定义了一个由10个元素所构成的一维数组变量p,它的每个元素是一个int *型指针。 5. 指针与函数 l 指向函数的指针(函数指针)。格式:<返回类型> (*<指针变量>) (<形式参数表>);如下面定义了一个函数指针变量fp: double (*fp)(int); fp可以指向返回类型为double,参数类型为int的任何函数。 l 注意:不要把函数指针与返回指针的函数混淆了。如下面声明了一个返回值为double*类型的函数func: double *func(int); l 对于一个函数,可以用取地址操作符&来获得它的内存地址或直接用函数名来表示。 l 可以用typedef为函数指针类型取一个名字,然后再用该函数指针类型来定义函数指针变量。函数指针类型名的定义格式如下: typedef <返回类型> (*<函数指针类型名>) (<参数表>); 例如上面的函数指针变量fp可以按照下面的形式来定义: typedef double (*FP) (int); //FP:函数指针类型 FP fp; //fp:函数指针变量 我们可以通过一个函数指针来调用它所指向的函数,调用格式为: (*<函数指针变量>)(<实参表>) 或 <函数指针变量>(<实参表>) 如下面的操作表示调用fp所指向的函数: (*fp)(10) 或 fp(10) l C/C++允许在调用一个函数时把一个函数作为参数传给被调用函数,这时,被调用函数的形参定义为一个函数指针类型,调用时的实参为一个函数的地址。 6. 多级指针 在定义指针变量时,如果指针变量所指向的变量的类型为指针类型,则为多级指针,如 下面的p就是一个多级指针类型; int x = 0; int *p = &x; int **q = &p; ***************************************************************************************** 说明: 2011.8.30 ~小峰 |
|