第六章 指针与数组
6.1 指针与地址
6.5 模板
6.4 多维数组与指针
6.3 数组与指针
6.2 this 指针
6.10 Windows对象句柄
6.9 复杂指针及其他
6.8 函数指针及其应用
6.7 指针数组
6.6 排序与查找
6.1 指针与地址
6.1.1指针的概念
6.1.2 指针变量的赋值、初始化与简单应用
6.1.1 指针的概念按变量的地址直接存取变量的方法称为,直接访问,方式 。 存贮变量的内存空间的首地址称为该变量的地址 。
如果将一个变量的地址放在另一个变量中,则存放地址的变量称为 指针变量 。 这样存取变量,也可以间接的由指针变量取得该变量的地址进行,称为,间接访问,方式 。 由于指针变量中的值是另一个变量的地址,我们习惯上形象地称为指针变量指向该变量 。
指针变量中的值也简称为 指针,所以 指针就是地址 。
指针类型可以按它指向的变量的类型区分。
指针类型变量定义语句格式如下:
<存贮类型 > 类型 *变量名 ;
这里的 *是一个定义变量为指针的说明符,而不是指针变量的一部分,
6.1.2 指针变量的赋值、初始化与简单应用
“&”取地址运算符,作用于内存中一个可寻址的数据 ( 如:变量,对象和数组元素等等 ),操作的结果是获得该数据的地址 。
,*” 间接引用( dereference)运算符,作用于一个指针类型的变量,访问该指针所指向的内存数据。
6.1.2 指针变量的赋值、初始化与简单应用
【 例 6.1】 指针赋值实例:
#include <iostream>
using namespace std;
void main(){
int age1=18,age2=20,*p_age;
p_age=&age1; //情况 1,见图 6.1
cout<<"age of wang ping is"<<*p_age<<endl;
p_age=&age2; //情况 2,指针改指另一变量,见图 6.1
cout<<"age of zhang ling is"<<*p_age<<endl;
20
age2
18
age1
p_age
6.1.2 指针变量的赋值、初始化与简单应用请看以下几个指针使用的例子:
int *p,*q,k=5;
p=&k //合法,p指向 k
q=p //合法,p和 q都指向 k
p=0 //合法,p悬空
p=&5 //非法,常量不可寻址
p=(int *)0x2700 //不允许直接干预内存分配
6.2 this 指针在上一小节,我们讨论了指向对象和结构变量的指针 。
当我们在对象的外部访问该对象的公有成员时,必须指明是哪一个对象 。 但是当我们用对象的成员函数来访问本对象的成员时,在成员函数中只要给出成员名就可以实现对该对象成员的访问 。 再进一步可用同一个类创建很多个对象,但它们共用同一份成员函数的拷贝 。 既然是同一份拷贝,那么成员函数又怎么知道是取哪一个对象的成员数据呢? 其实当调用一个成员函数时,系统自动产生一个隐藏的指针,这个指针称为 this指针,它始终指向产生这个调用的对象,并将该指针作为一个参数自动传递给该成员函数 。 这就是说,成员操作符总是要使用的,只不过在对象内是隐式的,而在对象外是显式的 。 即在对象内省略了 this指针 。
6.2 this 指针实际上编译器是这样实现 this指针的
1,改变类成员函数的定义,用附加参数 this指针来定义每个成员函数 。 如:
void Cgoods::Register Goods(Cgoods*this,char*nam,int
amount,float price){
strcpy(this->Name,name);this->Amount=amount;
this->price=price;
}
2,每个类成员函数的调用,加上一个附加的实参 ——被调用对象的地址 。
如:
Car1.RegisterGoods ( String,number,pr );
改变为:
RegisterGoods ( &Car1,String,number,pr);
6.3 数组与指针
6,3,1 数组与数组元素
6,3,3 指针、数组名作为函数参数
6,3,2 数组名、指针和指针运算
6,3,4 字符串处理
6.3.1 数组与数组元素数组的应用是非常广泛的,在数据结构中称为线性表 ( list),下面我们看一个对数组最简单的操作 。
【 例 6.3】 找出一个整型数组各数组元素中的最大数和最小数,
数组中的数由随机数函数 rand()产生 。
程序 Ex6_3.cpp
6.3.1 数组与数组元素
#include <iostream>
using namespace std;
#include <cstdlib>
const size=15;
void main()
{
int arr[size];
int i,high,low;
for (i=0;i<size;i++) arr[i]=rand()%100;
cout << "Here are the " <<size<<" roundom numbers,"<<endl;
for (i=0;i<size;i++)
cout<<arr[i]<<'\t';
cout<<endl;
high=arr[0]; //初始化时认为最大和最小值均为数组的第一个元素
low=arr[0];
for(i=1;i<size;i++)
{ if(arr[i]>high) high=arr[i];
if(arr[i]<low) low=arr[i];
}
cout<<"highest value is "<<high<<endl;
cout<<"lowest value is "<<low<<endl;
return;
}
6.3.1 数组与数组元素
【 例 6.4】 八皇后问题:在 8× 8的国际象棋棋盘上安放八个皇后,为避免她们之间相互攻击,要求没有任何两个皇后在棋盘的同一行,同一列及在同一对角线上 。 图 6.4是八皇后问题的一个解 。 八皇后在棋盘上可能有的布局数是,C864= 64!/ (8!× 56!)=4426165368种,
用回溯法解决八皇后问题显然是合适的 。
首先要求没有任何两个皇后在棋盘的同一行,则可每一行安置一个皇后,第 i 个皇后被安置在第 i行上 。 可用数组 queen[8] 中的每一个数组元素记录一个皇后所在位置的列号,在安置的过程中只需考虑每两个皇后不在同一列和同一对角线的问题 。 很明显第 i行皇后和第 j
行皇后在同一列的充要条件是:
queen[i]= queen[j]
第 i行皇后和第 j行皇后在同一对角线的充要条件是:
queen[i]-queen[j]= │i-j│
6.3.1 数组与数组元素初始时将每一行的皇后都放在第 0列 ( 即下标为 0的列 ),以第 0行皇后开始向下试探 。 设前 i-1 行的皇后已经安排得互不攻击,安排第 i行的皇后,
使之与前 i-1 行皇后也互不攻击,可从第 i行皇后的当前位置开始向棋盘的右部搜索:
1,若 queen[i] < 8,检查第 i行皇后是否与前 i-1 行皇后已经安排得互不攻击 。 无攻击将安排下一行 (第 i+1行 )皇后的位置;否则将该皇后向棋盘的右部移一列,重新进行这个过程 。
2,若 queen[i] >= 8,说明在前 i-1 行皇后的当前布局下,第 i行皇后已经无法安置 。 为此将第 i行的皇后回归到该行的开始列,回溯一行,考虑第 i-1行皇后与前面的 i-2个皇后都互不攻击的下一位置 。 如果已回溯到 i小于零的行,
程序结束 。
3,若当前安排好的皇后是在最后一行,说明已找到八个皇后互不攻击的一种布局,将这种布局输出 。 然后将最后一个皇后右移一列,重新进行这个过程,
以便找出下一种布局 。
程序 E x6_4.cpp
6.3.2 数组名、指针和指针运算我们已经讨论了指针与数组名的关系,数组名是指针常量,普通指针能进行更灵活的数组访问 。 通常把数组名赋给一个同类型 (指向数组元素 )的指针,由该指针来完成相关操作 。
【 例 6.5】 指针与数组相关的运算最后再次强调数组名是指针常量。
程序 Ex6_5.cpp
6.3.2 数组名、指针和指针运算
#include <iostream.h>
void main(){
int i,fibon[10]={0,1,1,2,3,5,8,13,21,34},*pfib1,*pfib2;
pfib1=pfib2=fibon;//也可以用 pfib1=pfib2=&fibon[0]
cout<<"使用数组显示斐波那契数列 "<<endl;
for(i=0;i<10;i++)
cout<<fibon[i]<<'\t'<<pfib1[i]<<endl;
cout<<"使用指针显示斐波那契数列 "<<endl;
for(i=0;i<10;i++)
cout<<*(fibon+i)<<'\t'<<*pfib2++<<endl;//注意,fibon++是错误的,
而 pfib2++是正确的
cout<<"显示指针相减,应为数组长度,";
cout<<pfib2-pfib1<<endl;//pfib2已指向数组末尾
}
6.3.3 指针、数组名作为函数参数
C++中函数的参数的基本使用方法是传值 。 为了弥补单纯传值的不足,以引用作为函数的参数,从逻辑上讲引用是别名,在函数中对参数的操作,就是对实参的操作,而在物理上是传实参的地址,
将指针用作函数的参数时,传的仍然是值,指针的值,这个值就是指针所指向的变量或对象的内存首地址,
在物理上讲我们传的是指针的值,与传其它变量是没有差异的,函数获得的是另一个变量的地址,在逻辑上讲我们是把另一个变量的地址传过去了,可以看作传地址。
6.3.3 指针、数组名作为函数参数
【 例 6.6】 本例用指针代替 【 例 5.3】 的引用实现两数据的交换
#include<iostream.h>
void swap(double *d1,double *d2){
double temp;
temp=*d1;*d1=*d2;*d2=temp;
}
void main(void){
double x,y;
cout<<”请输入 x和 y的值,<<?\n?;
cin>>x>>y;
swap(&x,&y);
cout<<”x=”<<x<<?\t?<<”y=”<<y<<endl;
}
6.3.3 指针、数组名作为函数参数数组名也是指针常量,当然可以作为函数的参数。在函数调用时传递实参数组的首地址,所以在被调函数中对形参数组的处理实际就是对调用函数的实参数组的处理。在被调函数中作为形式参数的一组数组不需要说明长度,即使说明了大小也不起作用,因为 C++只传递数组首地址,
而对数组边界不加检查。这带来的好处是,函数对长度不等的同类数组都通用。如要 指定长度可以设定另一个参数来传递数组元素的个数。
6.3.3 指针、数组名作为函数参数
【 例 6.7】 字符数组与字符数组相连接
#include <iostream.h>
void strcat(char s[],char ct[]){
int i=0,j=0;
while (s[i]!=0) i++;
while (ct[j]!=0) s[i++]=ct[j++];
s[i]='\0';
}
void main (void){
char a[40]="李明 ";
char b[20]="是东南大学学生 ";
strcat(a,b);
cout<<a<<endl;//打印字符数组 a
}
6.3.3 指针、数组名作为函数参数函数的返回值也可以是指针。如希望返回多个值,可以用引用参数或指针参数来等效实现,
如果我们希望返回一个数组,并且这个数组生命期不在该函数中消亡,我们可以返回一个指向该数组的指针。
6.3.4 字符串处理我们知道字符串是用字符型数组存储的,但两者也有差异,字符串要求其尾部以 ’ \0?作为结束标志 。 如:
char string[ ]=”C++ programming language”;
用 sizeof 来测 string长度为 25个字节,而实际串本身长度 (含空格 )为 24个字节,多出来的一个就是串结束符 ’ \0?(含 0)。
用指针处理字符串如下语句:
char *pstr=”C++ is a object_oriented language”;
6.3.4 字符串处理编译器将字符串常量,C++ is a object language”的第一个字符的存储地址赋给字符指针做初值 。 字符串与一般一维数组有一个明显不同:不对数组的边界进行检测,字符串有一个结束符 ’ \0?(全 0),在程序运行时是可以知道实际串长度的 。 如有语句:
cout<<a<<endl;
则打印出 a数组在内存中的首地址,一个 16进制的数 。 如有语句:
cout<<string<<?,?<<pstr<<endl;
则输出,C++ programming language,C++ is a object_oriented
language”;
C++标准库中有很多字符串处理函数,放在头文件 string.h中 (参见附录 )。
6.3.4 字符串处理下面介绍几个最常用的字符串处理函数:
1,字符串拷贝函数 char *strcpy(char *s,const
char ct)。
2,串连接函数 char *strcat(char *s,const char
*ct)。
3,字符串比较函数 int strcmp(const char *cs,const
char *ct)。
4,求字符串长度函数 int strlen(const char *s)。
6.3.4 字符串处理
【 例 6.8】 C++/C 标准库提供了字符串复制函数 strcpy(),本例显示算法的演变,
可见指针应用之妙 。
void scopy1(char s[],char ct[]) {
int i = 0;
while ( ct[i] !=?\0?){
s[i] = ct[i];
i ++;
}
s[i] =?\0?;}
由于数组与指针的等价性,同样可以用指针进行:
void scopy3(char *s,char *ct){
while(*ct) *s++ = *ct++;
*s =?\0?;
}
6.4 多维数组与指针
6,4,1 多维数组
6,4,2 指向多维数组的指针
6.4.1 多维数组数组不仅有一维数组,还有多维数组 。 一维数组可对应数学中的向量,而二维数组可对应矩阵,我们可用一个二维数组存储矩阵 。
二维数组的横向称为行,纵向称为列,上面这个数组为三行六列 。 定义二维数组的通用格式为:
,存储类型,类型 数组名 [行表达式 ] [列表达式 ];
1 3 5 7 9 11
2 4 6 8 10 12
3 5 7 11 13 17
6.4.1 多维数组
int matrix[3][6]
第 一 行 第 一 列 的 元 素 为 matrix[0][0],第 三 行 第 六 列 元 素 为
matrix[2][5],下标仍是从 0开始 。 一维数组在内存中存放时是按下标从小到大排列,计算机内存是一维编址的,多维数组必须要转化为一维方式存储,越右的下标变化越快,二维数组则按行排列,先排第一行,再排第二行,直到所有行排完:
matrix[0][0] matrix[0][1] matrix[0][2] matrix[0][3] matrix[0][4] matrix[0][5]
matrix[1][0] matrix[1][1] matrix[1][2] matrix[1][3] matrix[1][4] matrix[1][5]
matrix[2][0] matrix[2][1] matrix[2][2] matrix[2][3] matrix[2][4] matrix[2][5]
6.4.1 多维数组图 6.5 6*3*4的三维数组
6.4.1 多维数组
【 例 6.9】 矩阵运算:矩阵转置与矩阵相乘 。 下标作为参数传递 。
程序 Ex6_9.cpp
6.4.2 指向多维数组的指针指向行方向
x2d[0][0]
x2d[1][2]
x2d[0][2] x2d[0][3]
x2d[1][0] x2d[1][1]
x2d[2][0]
x2d[0][1]
x2d[2][3]x2d[2][1]
x2d[0]
x2d+1(或 pt+1)
x2d[2][2]
x2d[1]
x2d[2]
x2d[1][3]
x2d( 或 pt)
指向列方向
x2d+2(或 pt+2)
图 6.7 二维数组剖析
6.5 模板为了代码重用,代码就必须是通用的;通用的代码就必须不受数据类型的限制 。 那么我们可以 把数据类型改为一个设计参数 。 这种程序设计类型称为 参数化 (parameterize) 程序设计 。 软件模块由模板 (template) 构造 。 包括函 数 模 板 (function template) 和 类 模 板
(class template)。
6.5 模板
6.5.2 类模板与线性表
6.5.1 函数模板及应用
6.5.1 函数模板及应用函数模板可以用来创建一个通用功能的函数,以支持多种不同形参,简化重载函数的设计 。 函数模板定义如下:
template<模板参数表 > 返回类型 函数名 (形式参数表 ){
…… ; //函数体
}
<模板参数表 >( template parameter list)尖括号中不能为空,参数可以有多个,用逗号分开。模板参数主要是模板类型参数 。
6.5.1 函数模板及应用
【 例 6.12】 求数组元素中最大值的函数模板
template <typename Groap>Groap max(const Groap
*r_array,int size){
Groap max_val=r_array[0];
for (int i=1;i<size; ++i)
if(r_array[i]>ax_val) max_val=r_array[i];
return max_val;
}
把类型变量用具体的变量来代替称为模板实例化 (template
instantiation)。
6.5.1 函数模板及应用下面我们来完成 【 例 6.12】
#include <iostream.h>
template <typename Groap>Groap max(const Groap *r_array,int size){ Groap
max_val=r_array[0];
for (int i=1;i<size; ++i) if(r_array[i]>ax_val) max_val=r_array[i];
return max_val;}
int ia[5]={10,7,14,3,25};
double da[6]={10.2,7.1,14.5,3.2,25.6,16.8};
void main() {
int i=max(ia,5); //也可以写作 max(ia)
cout <<"整数最大值为,"<<i<<endl;
double d=max(da,6); //也可以写作 max(da)
cout <<"实数最大值为,"<<d<<endl;}
6.5.2 类模板与线性表在运算符重载中,我们提到在标准库中复数类型是一个类模板 。 类模板定义如下:
template<类型模板或参数表 > class 类名 {
…… //类声明体
};//再次指出分号不可少
template<模板参数表 > 返回类型 类名 <类型模板或参数表 >::成员函数名 1(形参表 )

…… ; //成员函数定义体

……
template<模板参数表 > 返回类型 类名 <类型模板或参数表 >::成员函数名 n(形参表 )

…… ; //成员函数 n定义体
}
6.5.2 类模板与线性表从通用的类模板定义中生成类的过程称为模板实例化 ( template instantiation),其格式为:
类名 <类模板实在参数表 > 对象名;
6.5.2 类模板与线性表线性表是数据结构中的概念,线性表实际包括顺序表和链表( linked list)。
对顺序表的典型操作包括:计算表长度,寻找变量或对象 x(其类型与表元素相同)在表中的位置(下标值),
判断 x是否在表中,删除 x,将 x插入列表中第 i个位置,
寻找 x的后继,寻找 x的前驱,判断表是否空,判断表是否满(表满不能再插入数据,否则会溢出,产生不可预知的错误),取第 i个元素的值。
我们把这些操作与数组封装在一起可以定义一个类,
考虑到数组元素的类型可以各不相同,所以定义为类模板。
6.5.2 类模板与线性表
1431
3 4 10965 87
24 16
2
18 9 277
11
85
1 12
11
0 13
1 2 3 4i
图 a
下标
1431
3 4 10965 87
24 16
2
18 9 7
11
85
1 12
11
0 13
图 b
下标图 6.8 从表中删除一个数据
6.5.2 类模板与线性表
1431
3 4 10965 87
24 16
2
18 9 277
11
85
1 12
11
0 13
5 4 3 2
i
图 a
下标
1
1431
3 4 10965 87
24 16
2
18 9 7
11
85
1 12
11x
0 13
图 b
下标图 6.9 向表中插入一个数据
6.5.2 类模板与线性表
【 例 6.13】 顺序表类模板程序 Ex6_13.cpp
6,6 排序与查找
6.6.2 常用的排序法
6.6.1 常用查找方法
6.6.1 常用查找方法图 6.10和图 6.11描述对半查找是怎样进行的。这里是升序的有序表。
low
8 9 171311 207 19 21 23 3126 292 5 37 3923
查找 low mid hig
h
20 21 292623 31 37 39
mid hig
h
low
20 21 23
mid hig
h23
low mid high 成功图 6.10 查找成功例
6.6.1 常用查找方法
2 5 7 8 11 13 179 19 20 2321 26 29 31 3710
查找 low
39
mid hig
h
2 5 7 8 11 13 179
low mid hig
h
11 13 179
low mid hig
h9
low mid high
图 6.11 查找失败例
6.6.1 常用查找方法
【 例 6.14】 对半查找递归算法,算法作为有序表
( ordered list) 模板类的成员函数 。
程序 E x6_14.cpp
6.6.1 常用查找方法散列 ( hash) 查找是最快的查找方法 。 前两种查找方法都是将查找关键字与表中的数据元素的值直接进行比较而达到查找目的的 。 如果能找到一个函数 f(key),将关键字经过函数的运算转换为数据表中的位置,直接将数据元素插入在该位置上 。 在查找中直接用该位置的数据元素与关键字值比较,以确定是否查到所要的数据元素 。 这样的组织称为散列,利用散列技术查找的方法称为散列查找,散列查找是一种直接查找 。 亦用音译称哈希查找 。
6.6.2 常用的排序法冒泡排序( Bubble Sort)是一个很有名的交换排序方法,其基本思想参见图 6.13
49 13 13 13 13 13 13 13
38 49 27 27 27 27 27 27
65 38 49 38 38 38 38 38
97 65 38 49 49 49 49 49
76 97 65 49? 49? 49? 49? 49?
13 76 97 65 65 65 65 65
27 27 76 97 76 76 76 76
49? 49? 49? 76 97 97 97 97
图 6.13从下往上扫描的冒泡程序
6.6.2 常用的排序法
【 例 6.18】 冒泡排序算法,作为 Orderedlist<T>类的成员函数程序 E x6_18.cpp
6.6.2 常用的排序法
template <typename T> void Orderedlist<T>::BubbleSort(){
bool noswap;
int i,j;
Node<T> temp;
for (i=0;i<last;i++){//最多做 n-1趟
noswap=true; //未交换标志为真
for(j=last;j>i;j--){//从下往上冒泡
if(slist[j].key<slist[j-1].key){
temp=slist[j];
slist[j]=slist[j-1];
slist[j-1]=temp;
noswap=false;
}
}
if(noswap) break; //本趟无交换,则终止算法 。
} }
6.6.2 常用的排序法选择排序( Selection Sort)的 基本思想 是:每一趟从待排序的记录中选出关键字最小的元素,顺序放在已排好序的子文件的最后,直到全部记录排序完成。
[49 38 65 97 76 13 27 49?]
13 [38 65 97 76 49 27 49?]
13 27 [65 97 76 49 38 49?]
13 27 38 [97 76 49 65 49?]
13 27 38 49 [76 97 65 49?]
13 27 38 49 49? [97 65 76]
13 27 38 49 49? 65 [97 76]
13 27 38 49 49? 65 76 97
图 6.14直接选择排序的过程
6.6.2 常用的排序法
【 例 6.19】 直接选择排序,作为 datalist <T>类的成员函数。
程序 Ex6_19.cpp
6.6.2 常用的排序法
template <typename T> void Orderedlist<T>::SelectSort(){
int i,j,k;
Node<T> temp;
for(i=0;i<last;i++){
k=i;
temp.key=slist[i].key;
for(j=i;j<=last;j++) if(slist[j].key<temp.key){
k=j;
temp.key=slist[j].key;
}
if(k!=i){
temp=slist[i];
slist[i]=slist[k];
slist[k]=temp;
}
} }
6.7 指针数组在上一节中我们学习了查找和排序 。 如果每一个数据元素很大,比如一个学生的简历,要排序也挺麻烦,去查找要在一个大范围中找也不方便 。 如果有一个索引,就象一本书的目录那就方便了,找到标题,再看一下页号,立即可以翻到 。 我们把页号看作地址即指针,每位同学的简历对应一个指针,构成一个数组,而把学生学号作为数组元素的下标,这样就形成了一个指针数组,找到学号对应元素,其所保存的指针值,
即简历的地址,查找起来要方便多了,称索引查找 ( Index Search) 。 参见下图
6.15用指针数组进行索引查找 。
下标 学号 姓名 性别 年龄
06002808 张伟 男 18
1 06002804 姚婕 女 17
2 06002802 王茜 女 18
3 06002807 朱明 女 18
4 06002809 沈俊 男 17
5 06002806 况钟 女 17
6 06002801 程毅 男 18
7 06002803 李文 男 19
8 06002805 胡凤 女 19
6.7 指针数组字符型的指针数组,这样的数组可以实现字符串数组的功能
name[5]
name[2]
name[0]
name[1]
name[3]
name[4]
name[6]
“Wednesday”
“Friday”
“Monday”
“Sunday”
“Tuesday”
“Thursday”
“Saturday”
图 6.16 字符指针数组
6.8 函数指针及其应用我们讨论过数组名的含义 。 那么函数名有什么含义呢?
每个函数在编译后,函数名对应于该函数执行代码的入口地址 。 所以对函数只能调用或取一个函数的入口地址 。 通过取地址运算符,&”就可以取得函数的入口地址 。 指向函数的指针可以作为函数的参数传递 。 指向函数的指针变量定义方式如下:
返回类型 (*指针变量名 )(参数表 )
由于一个函数不能以函数作为参数,所以当一个函数需要将函数作为参数时必须借用指向函数的指针。
6.8 函数指针及其应用
【 例 6.20】 梯形法求积分的函数 integer()是通用函数,
可求任一函数的定积分 。 不同的函数有不同的解析式,
该解析式决定了自变量在每一个小积分区间端点处的函数值 。 函数 integer()以一个指向函数的指针为参数,
由该参数调用欲求定积 分的函数,另两个参数是积分区间 。
程序 E x6_20.cpp
6.8 函数指针及其应用
void指针,又称无类型或泛型指针。任何类型的指针都可以赋给 void类型的指针变量,
6.8 函数指针及其应用
【 例 6.21】 对整型线性表或字符串线性表排序 。 比较函数分别是定义的 int_comp() 和 char_comp()。
程序 Ex6_21.cpp
6.9 复杂指针及其他在介绍二维数组合和指针的关系时已接触到二级指针的问题,
同样有多级指针可对应于多维数组,这种指针变量中存的是另一个指针变量的地址,其说明如下:
int val=10;
int *ptr=&val;
int **pptr=&ptr;
int **ppptr=&pptr; //是多少级指针就有多少 *号这里 val值为 10,*ptr值也为 10,**pptr的值和 ***ppptr的值均为 10。 注意这里的 *号与定义中的 *号意义不同,前者是指针说明符,后者是运算符,称间接引用运算符 。
6.9 复杂指针及其他
【 例 6.22】 多级指针 。
#include<iostream.h>
void main(){ int val=66;
int *pval = &val;
int **ppval = &pval;
cout<<"val="<<val<<'\n'<<"**ppval="<<**ppval<<'\n';
**ppval=18;
cout<<"val="<<val<<'\n'<<"**ppval="<<**ppval<<endl;
return;}
程序中 ppval称为多级指针,val,pval和 ppval之间的关系参见图 6.17。
变量 ppval 变量 pval 变量 val
图 6.17 多级指针
6.10 Windows对象句柄句柄的英文原文为 Handle,句柄的意思就是 代号 。
所谓“句柄”( handle)是 Windows操作系统中唯一标识某个
Windows对象(如程序实例、窗口、图表、光标、画刷、菜单等)的一个
32位无符号整数,句柄是 Windows对象的代号。只有有了句柄,程序才能使用与其对应的 Windows对象。 Windows程序通常通过调用 win32 API
函数来获得某个 Windows对象的句柄,只有有了句柄,程序才能使用与其对应的 Windows对象。形象地讲,对象是学生,句柄是学号。
Windows操作系统是由 C语言编写的。句柄的数据类型在 winnt.h中是这样说明的:
typedef void * HANDLE;
所以句柄是一个泛型(无类型)指针。
6.10 Windows对象句柄有了句柄,我们交给操作系统做的事,只要下一个指令(句柄)
操作系统就会给你干好,使操作变得非常简单。
当我们建立一个 Windows对象,自然就建立起一个实例句柄 。 需要使一个句柄无效只须撤消对该对象的引用,不要销毁这个句柄 。
win32支持的句柄类型有 58个,常见的见表 6.2。
句柄类型 说明 句柄类型 说明
HANDLE 一般句柄类型 HICON 图标句柄类型
HWND 窗口句柄类型 HCURSOR 光标句柄类型
HINSTANCE 程序实例句柄 HBRUSH 画刷句柄类型
HDC 设备描述表句柄 HPEN 画笔句柄类型
HMENC 菜单句柄类型 HFONT 字体句柄类型
HBITMAP 位图句柄类型 HFILE 文件句柄类型表 6.2 常用 Windows句柄类型