指针数组
由若干基类型相同的指针所构成的数组,称为 指针数组 (Pointer Array )。指针数组的每个元素都是一个指针,且这些指针指向相同数据类型的变量。
两个非常重要的等价形式
第一个:首地址+偏移量,表示值。
第二个:取地址运算,表示地址&*就抵消了。
指针和二维数组间的关系
一:将二维数组a看成一维数组,有2个int[3]型元素
此时指针代表行
/*a是一个二维数组
p=a; 把p初始化为a*/
p 第0行
p+i 第i行
*(p+i) 第i行第0列
*(p+i)+j 第i行第j列
*(*(p+i)+j) 取出第i行第j列的内容
二:把二维数组a看做一个维数组,有6个int型元素
指针代表列
指针和一维数组之间的关系
- 因数组名 a 代表数组的首地址,即元素 a[0]的地址(&a[0]),所以表达式 a+1 表示首地址后下一个元素的地址,即数组中的第 2个元素即下标为 1的元素 a[1]的地址(&a[1])。
- 由此可知,表达式a+i 代表数组中下标为 i的元素 a[i]的地址(&a[i])。已知数组元素的地址后,就可通过间接寻址来引用数组中的元素了。例如,*a或*(a+0)表示取出首地址a所指的存储单元中的内容,即元素 a[0],*(a+i)表示取出首地址元素后第 i 个元素的内容,即下标为i的元素 a[i]。数组元素 a[i]之所以能通过*(a+i)这种方法来引用,是因为数组的下标运算符[]实际上就是以指针作为其操作数的。例如,a[i]被编译器解释为表达式*(a+i),即表示引用数组首地址所指元素后面的第 i 个元素,而&a[i]表示取数组 a 的第 i+1 个元素的地址,它等价于指针表达式a+i。
- 如果指针变量 p指向了数组的首地址&a[0],那么*p 表示取出 p所指向的内存单元中的内容,即元素 a[0]的值,p+1 指向当前指针所指元素的下一个元素,p+i 指向当前指针下面的第i个元素,*(p+i)表示取出 p+i 所指向的内存单元中的内容,即元素 a[i]的值。虽然此时 a 和 p 的值都是数组的首地址,但不能像使用指针变量 p 那样对数组名 a 执行增1或减1操作。这是因为p是指针变量,可通过赋值操作改变它的值,使 p指向数组中的其他元素,而数组名a 是常量指针,代表一个地址常量,其值是不能被改变的。
当函数有数组a 作为形式参数时,*a 或 a[]哪种格式声明形式参数更好呢?
- 这个问题很棘手。从一种观点看,因为*a 是不明确的(函数是需要更多对象的数组还是指向单独对象的指针?)
- 所以 a[]是显而易见的选择。另一方面,因为*a 提示只是传递指针而不是复制数组,所以许多程序员辩称用*a 声明形式参数会更加精确。根据该函数是使用指针算术运算还是下标运算来访问数组的元素,其他函数在*a 和 a[]之间进行切换。在实践中,*a 比a[]更通用,所以最好使用前者。不知道是真是假,听说现在Dennis Ritchie 把符号a[]称为“活化石”,因为它“在使学习者困惑方面所起的作用同它提醒程序阅读者方面的作用是相同的”。
- 在当今的机器上,采用现今的编译器,数组下标方法常常是很好的,而且有时甚至会更好。底线是:学习这两种方法,然后
采用对你正在编写的程序更自然的方法。
为什么在形式参数的声明中*a 和a[]是一样的?
- 上述这两种形式都说明期望实际参数是指针。
- 在这两种情况下(特别是,指针算术运算和数组下标运算)可能在a 上的操作是相同的。(都是首地址+偏移量)
- 而且,在这两种情况下,可以在函数内给a赋予新的值。(虽然 C 语言允许数组名只作为“常量指针”,但是对于作为形式参数的数组名没有这类限制。)(c语言数组名只作为常量指针只读不写,但是作为形式参数的数组名是可以读写的)
二维数组的行指针和列指针有何区别?
- 二维数组中有两种指针。
- 一种是行指针,使用二维数组的行地址进行初始化;
- 例如,int (*p)[4];表示指针变量 p 可作为一个指向二维数组的行指针,它所指向的二维数组的每一行有 4 个元素。
- 对指向二维数组的行指针p进行初始化的方法为:p = a;或p = &a[0];
- 通过行指针p引用二维数组a 的元素 a[i][j]的方法可用以下 4种等价的形式: p[i][j] ←→ *(p[i]+j) ←→ *(*(p+i)+j) ←→ (*(p+i))[j]
- 另一种是列指针,使用二维数组的列地址进行初始化。
- 定义指向二维数组的列指针,可用如下定义方法:
- int *p;它可用3种等价的方式对其进行初始化。p = a[0]; ←→ p = *a; ←→ p = &a[0][0];
- 通过p引用二维数组a 的元素a[i][j]的方法是*(p+i*n+j)或p[i*n+j]。
- 如果 p 是二维数组的列指针,则通过列指针 p 引用二维数组 a 的元素 a[i][j],可将数组a 看成一个由(m 行×n 列)个元素组成的一维数组。
- 由于p代表数组的第 0行第 0列的地址,而从数组的第 0 行第 0 列寻址到数组的第 i 行第 j 列,中间需跳过 i*n + j 个元素,因此,p+i*n+j 代表数组的第 i 行第 j 列的地址,即&a[i][j],*(p+i*n+j)或 p[i*n+j]都表示 a[i][j]。注意,此时不能用 p[i][j]来表示数组元素,这是因为此时并未将这个数组看成二维数组,而是将二维数组等同于一维数组看待的,也就是将其看成了一个具有 m×n 个元素的一维数组。
/*区分指针数组与数组行指针的使用*/
/*区分指针数组与数组行指针的使用*/
#include <stdio.h>
main( )
{
int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
int (*p)[4], i, j;
/*(*p)[4]是定义一个指针变量 p 指向包含 4 个元素的一维数组,即行指针,
而不是指针数组,指针数组的定义是:*p[4]。注意它们的定义语法与含义不同。*/
p=a; /*p指向数组的行*/
for(i=0;i<3;i++){
for(j=0;j<4;j++)
printf("a[%d,%d]=%d\t", i, j, *(*(p+i)+j) );
printf("\n");
}
}
指针数组有哪些应用?
- 指针数组用于表示多个字符串,对多个字符串进行处理操作。因此在实际应用中,字符指针数组更为常用。虽然有时字符指针数组和二维字符数组能解决同样的问题,但涉及多字符串处理操作时,使用字符指针数组比二维字符数组更有效,例如相对于使用二维字符数组实现字符串物理排序的方法而言,使用字符指针数组实现字符串索引排序的程序执行效率相对更快一些。
- 指针数组用于表示命令行参数。在带参数的main()函数的定义中,有 argc和 argv两个形参。第1个形参argc被声明为整型变量,用于存放命令行中参数的个数,因程序名也是命令行参数,所以argc的值至少为 1。第2个形参 argv被声明为字符指针数组,用于接收命令行参数。这是指针数组的第2个重要应用。
p+1与p++是相同的操作吗?
- 如果定义了一个指向整型数据的指针变量 p,并使其值为数组 a 的首地址,那么通过这个指针变量 p也可访问数组 a 的元素
- 但 p+1与p++本质上是两个不同的操作,虽然二者都对指针变量 p进行加 1运算,但 p+1并不改变当前指针的指向,p仍然指向原来指向的元素,而 p++相当于执行 p = p + 1,因此 p++操作改变了指针 p 的指向,表示将指针变量 p 向前移动一个元素位置,即指向了下一个元素。
- p++并非将指针变量 p 的值简单地加 1,而是加上1*sizeof(基类型)个字节。例如,如果数组的基类型为 int,而 sizeof(int)为 4的话,那么 p++就相当于 p加上4个字节。
那么,在C 语言中“指针和数组等价”到底是什么意思?
- 说数组和指针“等价”-----不表示他们相同,甚至也不能互换。
- 它的意思是说数组和指针的算法定义使得可以用指针方便地访问数组或者模拟数组。
- 换言之,正如 Sayne Throop指出的“在C 语言中只是指针算术和数组下标运算等价,指针和数组是不同的”。
- 数组型形式参数和指针形式参数是可以互换的,但是作为数组变量不同于指针变量。
- 从技术上说,数组的名字不是指针。更确切地说,需要时 C 语言编译器会把数组的名字转换为指针。
- 为了更清楚地看出两者的区别,思考一下,当对数组a使用 sizeof 运算符时,会发生什么?sizeof(a)的值是数组中字节的总数,即每个元素的大小乘以元素的数量。但是,如果p 是指针变量,那么sizeof(p)的值则是用来存储指针值所需要的字节数量。
通过本例演示
-
指针和一维数组间的关系,
-
数组名的特殊意义及其在访问数组元素中的作用,
-
指针运算的特殊性及其在访问数组元素中的作用。
【例 11.1】下面程序用于演示数组元素的引用方法。
#include <stdio.h>
int main()
{
int a[5], i;
printf("Input five numbers:");
for (i=0; i<5; i++)
{
//scanf("%d", &a[i]); /* 用下标法引用数组元素 */
scanf("%d", a+i); /* 用指针法引用数组元素 */
}
for (i=0; i<5; i++)
{
//printf("%2d", a[i]); /* 用下标法引用数组元素 */
printf("%2d", *(a+i)); /* 用指针法引用数组元素 */
}
printf("\n");
return 0;
}
通过p指针来访问数组元素
【例11.2】下面程序用于演示数组和指针变量作为函数参数,实现的功能与例11.1 相同。
【例题解析】本例用于演示数组和指针作为函数参数进行按地址调用中的相似性。
#include <stdio.h>
void InputArray(int a[], int n);
void OutputArray(int a[], int n);
int main()
{
int a[5];
int *p = a;
printf("Input five numbers:");
InputArray(p, 5); /* 用指向一维数组的指针变量作为函数实参 ,p是数组首地址,相当于传过去了a的首地址*/
OutputArray(p, 5); /* 用指向一维数组的指针变量作为函数实参 */
return 0;
}
//形参声明为数组
//void InputArray(int a[], int n) /* 形参声明为数组,输入数组元素值 */
//{
// int i;
// for (i=0; i<n; i++)
// {
// scanf("%d", &a[i]); /* 用下标法访问数组元素 */
// }
//}
//void OutputArray(int a[], int n) /* 形参声明为数组,输出数组元素值 */
//{
// int i;
// for (i=0; i<n; i++)
// {
// printf("%4d", a[i]); /* 用下标法访问数组元素 */
// }
// printf("\n");
//}
//形参声明为指针变量
void InputArray(int *pa, int n) /* 形参声明为指针变量,输入数组元素值 */
{
int i;
for (i=0; i<n; i++, pa++)
{
scanf("%d", pa); /* 用指针法访问数组元素 */
}
}
void OutputArray(int *pa, int n) /* 形参声明为指针变量,输出数组元素值 */
{
int i;
for (i=0; i<n; i++, pa++)
{
printf("%4d", *pa); /* 用指针法访问数组元素 */
}
printf("\n");
}
【例11.3】编写程序,输入一个 2 行2列的二维数组,然后输出这个二维数组的元素值。
【例题解析】本例用于演示指针和二维数组间的关系。二维数组方法、行指针方法、列指针方法
#include <stdio.h>
#define N 4
void InputArray(int p[][N], int m, int n);
void OutputArray(int p[][N], int m, int n);
int main()
{
//二维数组写法
int a[3][4];
printf("Input 2*2 numbers:\n");
InputArray(a, 2, 2); /* 向函数传递二维数组的第0行的地址 */
OutputArray(a, 2, 2); /* 向函数传递二维数组的第0行的地址 */
return 0;
//行指针代表二维数组写法
// int a[2][2];
// int (*p)[2];
// p=a;/*或者p=&a[0]*/
// printf("Input 2*2 numbers:\n");
// InputArray(p, 2, 2); /* 向函数传递二维数组的第0行的地址 */
// OutputArray(p, 2, 2); /* 向函数传递二维数组的第0行的地址 */
// return 0;
//列指针写法,看做4个元素的一维数组
// int a[2][2];
// int *p;
// p=a;/*或者p=&a[0]*/
// printf("Input 2*2 numbers:\n");
// InputArray(p, 2, 2); /* 向函数传递二维数组的第0行的地址 */
// OutputArray(p, 2, 2); /* 向函数传递二维数组的第0行的地址 */
// return 0;
}
/* 形参声明为列数已知的二维数组,输入数组元素值 */
//void InputArray(int p[][N],int m, int n)
/* 形参声明为指向二维数组的列指针,输入数组元素值 */
//void InputArray(int *p, int m, int n)
/* 形参声明为指向列数已知的二维数组的行指针,输入数组元素值 */
void InputArray(int (*p)[N], int m, int n)
{
int i, j;
for(i = 0; i<m; i++)
{
for(j = 0; j<n; j++)
{
scanf("%d", &p[i][j]);
}
}
}
/* 形参声明为列数已知的二维数组,输出数组元素值 */
//void OutputArray(int p[][N], int m, int n)
/* 形参声明为指向二维数组的列指针,输出数组元素值 */
//void OutputArray(int *p, int m, int n)
/* 形参声明为指向列数已知的二维数组的行指针,输入数组元素值 */
void OutputArray(int (*p)[N], int m, int n)
{
int i, j;
for(i = 0; i<m; i++)
{
for(j = 0; j<n; j++)
{
printf("%4d", p[i][j]);
}
printf("\n");
}
}
【例11.4】使用指针数组重新编写第10章例 10.4 程序国名字典序排序。
【例题解析】因指针数组的元素是一个指针,所以在使用指针数组之前必须对数组元素进行初始化(pStr[i] = name[i]; )。通过移动字符串在实际物理存储空间中的存放位置而实现的排序,称为物理排序,而通过移动字符串的索引地址实现的排序(交换地址),称为索引排序。后者通过指针数组实现,这是指针数组的一个重要应用。
#include <stdio.h>
#include <string.h>
#define MAX_LEN 10 /* 字符串最大长度 */
#define N 150 /* 字符串个数 */
void SortString(char *ptr[], int n);
int main()
{
int i, n;
char name[N][MAX_LEN]; /* 定义二维字符数组 */
char *pStr[N]; /* 定义字符指针数组 */
printf("How many countries?");
scanf("%d", &n);
getchar(); /* 读走输入缓冲区中的回车符 */
printf("Input their names:\n");
for (i=0; i<n; i++)
{
pStr[i] = name[i]; /* 让pStr[i]指向二维字符数组name的第i行 */
gets(pStr[i]); /* 输入第i个字符串到pStr[i]指向的内存 */
}
SortString(pStr, n); /* 字符串按字典顺序排序 */
printf("Sorted results:\n");
for (i=0; i<n; i++)
{
puts(pStr[i]); /* 输出排序后的n个字符串 */
}
return 0;
}
/*函数功能:用指针数组作函数参数,采用交换法实现字符串按字典顺序排序 */
void SortString(char *ptr[], int n)
{
int i, j;
char *temp = NULL; /* 因交换的是字符串的地址值,故temp定义为指针变量 */
for (i=0; i<n-1; i++)
{
for (j = i+1; j<n; j++)
{
if (strcmp(ptr[j], ptr[i]) < 0) /* 交换指向字符串的指针 */
{
temp = ptr[i];
ptr[i] = ptr[j];
ptr[j] = temp;
}
}
}
}
【例11.6】编程输入某班学生的某门课成绩,计算并输出其平均分。学生人数由键盘输入。
【例题解析】本例用于演示长度可变的一维动态数组。
#include <stdio.h>
#include <stdlib.h>
void InputArray(int *p, int n);
double Average(int *p, int n);
int main()
{
int *p = NULL, n;
double aver;
printf("How many students?");
scanf("%d", &n); /* 输入学生人数 */
p = (int *) malloc(n * sizeof(int)); /* 向系统申请内存 */
if (p == NULL) /* 确保指针使用前是非空指针,当p为空指针时结束程序运行 */
{
printf("No enough memory!\n");
exit(1);
}
printf("Input %d score:", n);
InputArray(p, n); /* 输入学生成绩 */
aver = Average(p, n); /* 计算平均分 */
printf("aver = %.1f\n", aver); /* 输出平均分 */
free(p); /* 释放向系统申请的内存 */
return 0;
}
/* 形参声明为指针变量,输入数组元素值 */
void InputArray(int *p, int n)
{
int i;
for (i=0; i<n; i++)
{
scanf("%d", &p[i]);
}
}
/* 形参声明为指针变量,计算数组元素的平均值 */
double Average(int *p, int n)
{
int i, sum = 0;
for (i=0; i<n; i++)
{
sum = sum + p[i];
}
return (double)sum / n;
}
带参数的main函数:
看到argv 的声明用**argv代替了*argv[]。这是否合法?
当然合法。在声明形式参数时,不管 a 的元素是什么,*a 的写法和 a[]的写法总是一样的。
内存耗尽怎么办?
- 如果在申请动态内存时找不到足够大的内存块,malloc 和 new 将返回 NULL 指针,宣告内存申请失败。通常有三种方式处理“内存耗尽”问题。
- 判断指针是否为 NULL,如果是则马上用 return 语句终止本函数。(不适用用于一个函数内多处动态内存申请)
- 判断指针是否为 NULL,如果是则马上用 exit(1)终止整个程序的运行。
- 为 new 和 malloc 设置异常处理函数。例如 Visual C++可以用_set_new_hander函数为 new 设置用户自己定义的异常处理函数,也可以让 malloc 享用与 new 相同的异常处理函数。详细内容请参考C++使用手册。
- 上述(1)(2)方式使用最普遍。如果一个函数内有多处需要申请动态内存,那么方式(1)就显得力不从心(释放内存很麻烦),应该用方式(2)来处理。很多人不忍心用 exit(1),问:“不编写出错处理程序,让操作系统自己解决行不行?”不行。如果发生“内存耗尽”这样的事情,一般说来应用程序已经无药可救。如果不用exit(1) 把坏程序杀死,它可能会害死操作系统。
发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。有时用户怒气冲冲地把你找来,程序却没有发生任何问题,你一走,错误又发作了。
常见的内存错误及其对策如下:
- 内存分配未成功,却使用了它。编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为 NULL。如果指针 p 是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用 malloc 或 new 来申请内存,应该用 if(p==NULL)或if(p!=NULL)进行防错处理。
- 内存分配虽然成功,但是尚未初始化就引用它。犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。
- 内存分配成功并且已经初始化,但操作越过了内存的边界。例如在使用数组时经常发生下标“多 1”或者“少 1”的操作。特别是在 for 循环语句中,循环次数很容易搞错,导致数组操作越界。
- 忘记了释放内存,造成内存泄露。含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。动态内存的申请与释放必须配对,程序中malloc与free 的使用次数一定要相同,否则肯定有错误(new/delete 同理)。释放了内存却继续使用它。有三种情况:
- 使用 free 或 delete 释放了内存后,没有将指针设置为 NULL。导致产生“野指针”。
- 函数的 return 语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
- 程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
- 【规则1】用 malloc 或 new 申请内存之后,应该立即检查指针值是否为 NULL。防止使用指针值为NULL的内存。
- 【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
- 【规则3】避免数组或指针的下标越界,特别要当心发生“多 1”或者“少 1”操作。
- 【规则4】动态内存的申请与释放必须配对,防止内存泄漏。(何谓内存泄漏?内存泄漏(Memory Leak)是指用动态内存分配函数动态开辟的存储空间,在使用完毕后未被释放,结果导致一直占据该内存单元,直到程序结束。)
- 【规则5】用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产生“野指针”。(何谓野指针?野指针是NULL指针吗?“野指针”不是 NULL 指针,是指向“垃圾”内存(不可用内存)的指针。释放内存的结果只是改变了内存中存储的数据,使该内存存储的内容变成了垃圾。释放了内存但却仍然继续使用它,将导致产生“野指针”。)
C 程序中变量的内存分配方式有哪几种?
- 从静态存储区分配:程序的全局变量和静态变量都在静态存储区上分配,在程序运行期间始终占据这些内存,仅在程序终止前,才被操作系统收回。静态变量和全局变量保存在数据段中
- 在栈上分配:在执行函数调用时,系统在栈上为函数内的局部变量及形参分配内存,函数执行结束时,自动释放这些内存。自动存储类别的局部变量保存在栈中
- 从堆上分配:在程序运行期间,用动态内存分配函数来申请的内存都是从堆上分配的。用动态内存分配函数申请的内存在堆中
- C 程序内存映像由地址低端到地址高端分别包括如下区域:代码段(存放程序的机器代码和只读数据)、数据段(程序中的静态数据)、堆(动态内存分配函数申请的动态内存),栈(保存函数执行的现场环境)。
如何使用动态内存分配函数实现动态数组?
动态内存分配 (Dynamic Memory Allocation )是指在程序运行时为变量分配内存的一种方法
动态数组 (Dynamically Allocated Array )是指在程序中不事先指定数组的大小,在程序运行过程中确定数组的大小。使用动态数组的优点是可以根据用户需要,有效利用存储空间。
使用动态内存分配函数实现有n个 int型元素的一维动态数组,可使用
p = (int *) malloc(n * sizeof(int)); /* 向系统申请内存 */
if (p == NULL) /* 确保指针使用前是非空指针,当 p 为空指针时结束程序运行 */
{
printf("No enough memory!\n");
exit(1);
}
p = (int *) calloc(n, sizeof(int)); /* 向系统申请内存 */
if (p == NULL) /* 确保指针使用前是非空指针,当 p 为空指针时结束程序运行 */
{
printf("No enough memory!\n");
exit(1);
}
使用动态内存分配函数实现有m*n 个 int型元素的二维动态数组,可使用
p = (int *)calloc(m*n, sizeof(int)); /* 向系统申请内存 */
if (p == NULL) /* 确保指针使用前是非空指针,当 p 为空指针时结束程序运行 */
{
printf("No enough memory!\n");
exit(1);
}
calloc()与malloc()有何不同?
函数calloc()用于给若干同一类型的数据项分配连续的存储空间并赋值为 0。
例如,假设有:
float *pf = NULL;
此时,虽然
pf = (float *)calloc(10, sizeof(float));
与
pf = (float *)malloc(10*sizeof(float));
都表示向系统申请 10 个连续的 float 型存储单元,并用指针 pf 指向该连续内存的首地址,但从安全的角度考虑,使用 calloc()更明智,因为与 malloc()不同的是 calloc()能自动将分配的内存初始化为0。函数calloc 把内存块初始化为“零位”,这是否意味着内存块中的全部数据项都变为零了?通常是,但不总是这样的。把整数设置成零位会始终使整数为零。把浮点数设置为零位会使数为零,但这是不能保证的,要依赖于浮点数的存储方式。此问题类似于指针的情况,指针各位置为零并不一定是空指针。(浮点数的存储方式不同,可能就不是0,但通常都是0的)
int *a[3];
a = (int*) malloc(sizeof(int)*3);
free(a);
问题解答
int *a[3];定义的是指针数组,所以不能为a从新分配内存。
强制类型转换malloc 或者其他内存分配函数的返回值,是否有什么好处呢?
虽然一些程序员都这样做,但不是真的有什么好处,强制类型转换这些函数返回 void *型的指针并不是标准 C 必需的,因为 void *型的指针会在赋值操作时自动转换为任何指针类型。对返回值进行强制类型转换的习惯来自于经典 C。在经典 C 中,内存分配函数返回char *型的值,用强制类型转换实现是必要的。
void*指针是 ANSI C 新标准中增加的一种指针类型,具有一般性,通常称为通用指针(Generic Pointer),常用来说明其基类型未知的指针,即声明了一个指针变量,但未指定它可以指向哪一种基类型的数据。因此,若要将函数调用的返回值赋予某个指针,则应先根据该指针的基类型,用强转的方法将返回的指针值强转为所需的类型,然后再进行赋值操作。
通过移动字符串在实际物理存储空间中的存放位置而实现的排序,称为字符串的物理排序。
通过移动字符串的索引地址实现的排序,称为字符串的 索引排序。