chapter 6 指针
C语言程序设计
chapter 6 指针
1. 何谓指针(Pointer)
编译系统根据程序中定义的变量类型,分配一定长度的空间,也就是我们所说的内存。
而内存是具有唯一地址的,CPU会通过内存寻址方式对内存中的某个指定数据的地址进行定位,从而找到这个数据。
指针就是用来保存这个地址的变量,可以通过访问指针(也就是访问这个地址),从而达到访问这个数据的目的。
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。
由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为"指针"。意思是通过它能找到以它为地址的内存单元。
在高级语言中,指针有效地取代了在低级语言,如汇编语言与机器码,直接使用通用暂存器的地方,但它可能只适用于合法地址之中。
指针参考了存储器中某个地址,通过被称为反参考指针的动作,可以取出在那个地址中存储的值。
作个比喻,假设将电脑存储器当成一本书,一张内容记录了某个页码加上行号的便利贴,可以被当成是一个指向特定页面的指针;
根据便利粘贴面的页码与行号,翻到那个页面,把那个页面的那一行文字读出来,就相当于是对这个指针进行反参考的动作。
在信息工程中指针是一个用来指示一个内存地址的计算机语言的变量或中央处理器(CPU)中寄存器(Register)【用来指向该内存地址所对应的变量或数组】。
指针一般出现在比较接近机器语言的语言,如汇编语言或C语言。面向对象的语言如Java一般避免用指针。
指针一般指向一个函数或一个变量。
在使用一个指针时,一个程序既可以直接使用这个指针所储存的内存地址,又可以使用这个地址里储存的函数的值。
另外,指针也指钟表中用来指示对应时间的部件。
当然,我们这里的指针是指程序语言中的指针变量,是一个指向某一个地址的变量。
平常我们可以通过变量名去访问某个元素,那么这样的访问方式是:直接访问
而我们用一个指针变量来存放变量名的地址,通过访问指针变量的方式,找到原变量的地址,从而访问该变量对应的元素的访问方式是:间接访问
指针指向的是地址,那么我们可以说变量 a的指针是 2000,而不能说 变量 a的指针变量是 2000,指针是一个地址,指针变量是一个存放地址的变量。
2. 指针变量的定义
定义格式:类型名 * 指针变量名;
int a=1; float b=3.14; double c=3.1415926; char d='A';
int * pointer1; //pointer1 是指向 int 类型的指针变量,简称 int 指针
float * pointer2; //pointer2 是指向 float 类型的指针变量,简称 float 指针
double * pointer3; //pointer3 是指向 double 类型的指针变量,简称 double 指针
char * pointer4; //pointer4 是指向 char 类型的指针变量,简称 char 指针
//上述左边 int/float/double/char 是 pointer1~4 的基类型,声明时就必须指定
pointer1 = &a; //将变量 a的地址赋给 pointer1,且包含着 a 是 int 类型的信息
pointer2 = &b; //将变量 b的地址赋给 pointer2,且包含着 b 是 float 类型的信息。
pointer3 = &c; //将变量 c的地址赋给 pointer3,且包含着 c 是 double 类型的信息。
pointer4 = &d; //将变量 d的地址赋给 pointer4,且包含着 d 是 char 类型的信息。
也可以使用如下方式:
int * pointer1 = &a;
float * pointer2 = &b;
double * pointer3 = &c;
char * pointer4 = &d;
int *arr[10]; //声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向 int 类型的指针
int (*arr)[10]; //声明一个数组指针,该指针指向一个 int 类型的一维数组
int **pointer; //声明一个指针 pointer ,该指针指向一个 int 类型的指针
#include<stdio.h>
void swap(int *pointer1, int *pointer2){
int temp = *pointer1;
*pointer1 = *pointer2;
*pointer2 = temp;
}
int main(){
int a=1,b=2;
int*pointer1=&a, *pointer2=&b;
printf("a=%d\t b=%d\n", a, b);
printf("pointer1=%d\t pointer2=%d\n", pointer1, pointer2);
swap(pointer1, pointer2);
printf("a=%d\t b=%d\n", a, b);
printf("pointer1=%d\t pointer2=%d\n", pointer1, pointer2);
printf("*pointer1=%d\t *pointer2=%d\n", *pointer1,*pointer2);
}
输出结果:
a=1 b=2
pointer1=6618636 pointer2=6618632
a=2 b=1
pointer1=6618636 pointer2=6618632
*pointer1=2 *pointer2=1
3. 通过指针引用数组
int a[10]={0,1,2,3,4,5,6,7,8,9};
int *pointer1 = &a[0]; //将数组a的首元素地址赋指针变量 pointer1
int *pointer2 = a; //将数组a的首元素地址赋指针变量 pointer2
在指针 p 指向一个数组元素时,可以对指针进行以下运算:
p+1; //指向同一数组中的下一个元素
p++; //指向同一数组中的下一个元素
++p; //指向同一数组中的下一个元素
p-1; //指向同一数组中的上一个元素
p--; //指向同一数组中的上一个元素
--p; //指向同一数组中的上一个元素
p1-p2; //只有p1,p2都指向同一数组中的元素时才有意义,否则无实际意义
如:int a[10]={0,1,2,3,4,5,6,7,8,9};
int *p = a;
*(p+i) 或 *(a+i) 都等同于 a[i]
[]实际上是变址运算符,即 a[i] 按 a+i 计算地址,然后找出此地址单元中的值。
#include<stdio.h>
int main(){
int a[10]={1,2,3,4,5,6,7,8,9,0};
for(int *p=a; p<(a+10); p++){
printf("%d ", *p); //1 2 3 4 5 6 7 8 9 0
}
return 0;
}
数组名用作函数参数
void print(int arr[], int l, int r);
void print(int arr[], int l, int r){
for(int i=l; i<=r; i++){
printf("%d ",arr[i]);
}printf("\n");
}
实参数组名代表一个固定的地址,或者说指针常量,但形参数组名并不是一个固定地址,而是按照指针变量处理。
所以形参可以用数组名,也可以使用指针变量名
void print(int *arr, int l, int r);
void print(int *arr, int l, int r){
for(int i=l; i<=r; i++){
printf("%d ",arr[i]);
}printf("\n");
}
4. 通过指针引用多维数组
#include<stdio.h>
#include<stdlib.h>
int a[10][10];
int main(){
int n=10;
for(int i=0; i<n; i++){
for(int j=0; j<=n; j++){
a[i][j]=i*j+1;
}
}
printf("地址:%d %d %d\n", a[0], *a, *(a+0));
printf("地址:%d %d %d\n", a, a+1, a+2);
printf("地址:%d %d %d\n", a[1], a[1]+1, a[1]+2);
printf("元素:%d %d %d\n", *a[0], **a, **(a+0));
printf("元素:%d %d %d\n", **a, **(a+1), **(a+2));
printf("元素:%d %d %d\n", *a[1], *(a[1]+1), *(a[1]+2));
printf("元素:%d %d %d\n", *a[1], *(*(a+1)+1), *(*(a+1)+2));
return 0;
}
输出结果:
地址:4223040 4223040 4223040
地址:4223040 4223080 4223120
地址:4223080 4223084 4223088
元素:1 1 1
元素:1 1 1
元素:1 2 3
元素:1 2 3
5. 通过指针引用字符串
#include<stdio.h>
int main(){
char str1[]="Hello C1";
char *str2 ="Hello C2";
printf("str1 = %s\n", str1);// Hello C1
printf("str2 = %s\n", str2);// Hello C2
printf("str1 = ");
for(char *p=str1; *p!='\0'; p++){
printf("%c", *p);
}printf("\n"); // Hello C1
printf("str2 = ");
for(char *p=str2; *p!='\0'; ){
printf("%c", *p++);
} printf("\n"); // Hello C2
char *pre=str1;
printf("*pre = %c\n", *pre); // H
printf("*pre++ = %c\n", *pre++); // H
printf("*pre = %c\n", *pre); // e
printf("*++pre = %c\n", *++pre); // l
printf("*pre = %c\n", *pre); // l
printf("++*pre = %c\n", ++*pre); // m
printf("*pre = %c\n", *pre); // m
// *pre++ 由于 * 和 ++ 同优先级,结合方向为 自右而左,因此等价与 *(pre++)
// 其执行是:先引用 pre 的值,实现 *pre 的运算,再使 pre++
// *(++pre) 是先对 pre++,再引用 pre 的值。
return 0;
}
6. 动态内存分配与它的指针变量
全局变量是分配在内存中的静态存储区的,非静态的局部变量(包括形参)是分配在内存中的动态存储区的,这个存储区是一个称为栈(stack)的区域。C语言允许建立动态存储分配区域,存放临时用的数据。这些临时数据存放在一个特别的存储区,称为堆(heap)区。由于未在声明的部分定义他们为变量或数组,因此不能通过变量名或数组名去引用这些数据,只能通过指针来引用。
对内存的动态分配时通过系统提供的库函数来实现的,主要用:
malloc,calloc,realloc,free这4个函数。
1.用malloc函数开辟动态存储区,其函数原型为:
void *malloc(unsigned int size);
malloc(100); // 开辟100字节的临时分配域,函数值为其第一个字节的地址
在内存的动态存储区分配一个长度为size的连续空间,形参size的类型为无符号整形。
2.用calloc函数开辟动态存储区,其函数原型为:
void *calloc(unsigned n, unsigned int size);
p=calloc(50,4); // 开辟50×4个字节的临时分配域,把起始地址赋给指针变量p
在内存中分配n个长度为size的连续空间。
3.用realloc函数重新分配动态存储区,其函数原型为:
void *realloc(void *p, unsigned int size);
recalloc(p, 50); // 将p所指向的已分配的动态空间改为50字节
如果已经通过 malloc 或 calloc 函数获得了动态空间,想改变大小,可以用 recalloc 函数重新分配。
4.用free函数释放动态存储区,其函数原型为:
void free(void *p);
free(p); // 释放指针变量 p 所指向的已分配的动态空间
其作用是释放指针变量 p 所指向的动态空间,使这部分空间能重新被其他变量使用。
p 应是最后一次调用 calloc或 malloc 函数时得到的函数返回值。
#include<stdio.h>
#include<stdlib.h>
int main(){
int *p1 = (int *)malloc(100); // 开辟 100 Byte 空间
int *p2 = (int *)malloc(100 * sizeof(int)); // 开辟 100个int 空间
int i=0;
// for(i=0; i<5; i++) scanf("%d", p2+i);
for(i=0; i<5; i++) printf("%d ", *(p2+i));
puts("");
int *p3 = (int *)calloc(50, 4);
*(p3+1) = 1;
printf("address = %x, value = %d\n", p3, *(p3+1));
int *p4 = (int *)realloc(p3, 5);
*p4 = 2;
printf("address = %x, value = %d\n", p4, *p4);
free(p4);
printf("address = %x, value = %d\n", p4, *p4);
return 0;
}
#include<stdio.h>
#include<stdlib.h>
#define N 100
//void *malloc(unsigned int size); // [0, size]
//void *calloc(unsigned n, unsigned int size);
//void *realloc(void *p, unsigned int size); // 将p所指向的已分配的动态空间改为size字节
//void free(void *p); // 释放指针变量 p 所指向的已分配的动态空间
int main(){
int *p1 = (int *)malloc(100); // 开辟100字节的临时分配域,函数值为其第一个字节的地址
*p1 = 1;
*(p1+1) = 2;
printf("%d %d\n", *p1, *(p1+1));
// 6 个 int 6*4 = 24 Byte
int *p2 = (int*)malloc(6*sizeof(int));// 开辟6 *sizeof(int) 字节的临时分配域,函数值为其第一个字节的地址
*p2 = 3;
*(p2+1) = 4;
printf("%d %d\n", *p2, *(p2+1));
int *p3 = (int*)calloc(50, sizeof(int)); // 开辟 50*sizeof(int) 个字节的临时分配域,把起始地址赋给指针变量p
*p3 = 5;
*(p3+1) = 6;
printf("%d %d %d\n",p3, *p3, *(p3+1));
// -------------------------------------------------------------------
int *p4 = (int*) realloc(p3, 100*sizeof(int)); // 原本首地址不变
*p4 = 7;
*(p4+1) = 8;
printf("%d %d %d\n",p3, *p3, *(p3+1));
printf("%d %d %d\n", p4, *p4, *(p4+1));
free(p1);
free(p2);
free(p3);
free(p4);
printf("%d %d %d\n", p4, *p4, *(p4+1));
return 0;
}