知识屋:更实用的电脑技术知识网站
所在位置:首页 > 教育

学习笔记(10):指针精讲(最详尽的使用指南)

发表时间:2022-03-25来源:网络

C语言-基础入门-学习笔记(10):指针精讲(最详尽的使用指南)

指针一直是我最难懂的地方,我相信也是最令大家头疼的地方,所以在这里我会带着大家详细地学习一下指针,希望大家能够通过这篇文章完全地搞懂指针是什么,不在受它的折磨,我也会将我看到的一些好的资料放出来供大家参考!!

一、指针概述

C语言具有获取变量地址和操纵地址的能力,而用来操作地址的这种特殊数据类型就是指针。
简单来说,指针就是一种数据类型,用来表示内存地址,因此使用指针变量就可以对内存进行灵活的操作。

1.访问内存的两种方式

直接访问:每一个被定义的变量都有两个属性:变量值和变量地址。变量地址表示了该变量在内存中存储的位置,变量值就是该内存中的内容。因此要访问该空间上的内容可以直接使用变量名。int a = 241; printf("%d",a); 间接访问:可以使用地址操作符 & 来获得变量的地址。再使用指针操作符来获得该地址的内容。int a = 241; *(&a);

2.指针的概念

表示内存地址的数据类型就是指针类型。所以,地址就是指针,一个变量的地址就是一个指针型常量,用来保存地址的变量就是一个指针型变量。

假设变量名为a,它的内容为241,它存储在了地址为0016的地方。
它的指针名字为b,由于地址就是指针,那么该指针的值为a的地址,即0016,指针的地址为1010。
所以这里b叫作指针变量。
指针变量的属性:
1、指针变量的地址,即为指针变量分配的内存空间地址。(1010)
2、指针变量的值,指针内存空间上的内容。(0016)
3、以指针变量的值为地址的空间地址,也称指针指向的空间地址。(0016)
4、指针指向的内存空间的内容。(241)

虽然指针变量的值和其指向空间地址的值相同,但指针变量的值为一个内存空间的内容,是可以改变的。而指针变量指向的空间的地址是一个内存空间的地址,是一个常量,不可以被改变。

3.指针变量的定义

指针类型由两部分组成:数据类型名和指针操作符。 其中数据类型名声明了指针变量指向的内存空间存储的数据类型。
数据类型名 * 指针变量名

char * cp;//指针变量cp指向一个char型内存空间 int * ip;//指针变量ip指向一个int型内存空间 float * fp;//指针变量fp指向一个Int型内存空间

范例1

#include int main(void){ printf("sizeof(int *) = %d\n",sizeof(int *)); printf("sizeof(char *) = %d\n",sizeof(char *)); printf("sizeof(float *) = %d\n",sizeof(float *)); return 0; }


指针类型所占空间与其指向的内存存储的数据类型无关。

二、指针的使用

1.指针变量的赋值

为指针变量赋值就是为指针设定它指向的内存空间的过程。给指针变量赋值必须赋给它一个地址。

char c = 'a'; char * cp; cp = &c;//指针指向c的地址,用&取c的地址 printf("%c",c); printf("%c",*cp);//这句打印与上面一句是等价的,都是将c打印出来

范例2
指针可以访问和修改变量

#include int main(void){ int a = 2; int *b; b = &a; printf("\ta = %d\n",a);//直接访问 printf("\t*b = %d\n",*b);//间接访问 *b = 3;//改变指向空间的内容 printf("After \"*b = 3\":\n"); printf("\ta = %d\n",a); printf("\t*b = %d\n",*b); return 0; }


范例3
指针变量的值在一次赋值后,可以再次被赋值为其他地址。
在输出符时,对于指针的值、地址符的输出用%p

#include int main(void){ int a = 2; int b = 3; int *p; printf("&a = %p\n",&a); printf("&b = %p\n",&b); printf("\nAfter definition:\n"); printf("\t&p = %p\n",&p); printf("\tp = %p\n",p); //printf("\t*p = %d\n",*p); p = &a;//将p赋值为a的地址 printf("\nAfter \"p = &a\":\n"); printf("\t&p = %p\n",&p); printf("\tp = %p\n",p); printf("\t*p = %d\n",*p); p = &b;//改变p的指向 printf("\nAfter \"p = &b\":\n"); printf("\t&p = %p\n",&p); printf("\tp = %p\n",p); printf("\t*p = %d\n",*p); return 0; }


对指针变量赋值的过程,就是使指针变量指向一个新的内存空间的过程。

2.将指针变量赋值为整数

原则上不能将整数数值赋值给指针变量,否则指针变量会指向以该整数数值为地址值的内存空间。
但是如果该整数值空间是有效的,那么可以使用。
范例4
使用scanf函数将整数赋值给指针变量

#include int main(void){ int a = 1; int *p; printf("a = %d\n",a); printf("&a = %p\n",&a);//输出地址时用%p printf("Input the address of a: "); scanf("%p",&p); printf("*p = %d\n",*p); printf("p = %p\n",p); return 0; }

3.初始化指针变量

使用指针时最好为其进行初始化,否则指针将指向一个不可知的空间。

int a = 0; int *p1 = &a; int *p2 = NULL;

NULL的值为0,它是指针型数值中约定的0值的表示形式。

4.const指针

指向const的指针变量int const *p; const int *p;

将变量p声明为指向存储const int型数据的内存空间的指针变量,该类指针指向的内存空间的内容是不可变的。

const int a = 1; const int *p1 = &a; *p1 = 2; //*p1已经为1,不能为2 const型指针变量int *const p;

const型指针变量指向的内存空间是固定的,初始化后不能将其指向其他空间。

int a = 1; int b = 2; int *const p = &a; *p = 12;//正确 p = &b;//指针变量的值不可改变,即不能改变指向的内存空间地址 指向const的const指针变量const int *const p;

该指针变量的值和该指针指向的空间的值都是不可改变的。

int a = 1; int b = 2; int const *const p = &a; *p = 12;//错误 p = &b;//错误

三、指针与函数

指针变量也可以作为函数参数使用。

1.指针形参

函数调用时,将实参的值赋给形参。使用指针变量作为函数参数,可以将一个内存空间的地址传递到函数中,可以通过该地址来操作该地址上的内存空间。

void func(int *pt,int a); int *p; int x,y; x = 5; y = 20; p = &x; func(p,y);

这里实参y传给形参a,则将a赋值为20;而p为x的地址,将p赋给指针*pt,则pt所指向的内容为5。

指针可以做到在函数内改变函数外的值,通过地址来传递值,这种方式称为地址传递。

范例5
使用指针作为形参的函数实现两个数的交换

#include void swap(int *pt1,int *pt2){ //输入一个中间变脸 int tmp; printf("pt1 = %p\n",pt1); printf("pt2 = %p\n",pt2); printf("*pt1 = %d\n",*pt1); printf("*pt2 = %d\n",*pt2); //用指针进行交换 tmp = *pt1; *pt1 = *pt2; *pt2 = tmp; printf("*pt1 = %d\n",*pt1); printf("*pt2 = %d\n",*pt2); } int main(void){ int a,b; int *p1 = NULL; int *p2 = NULL; printf("Please input two numbers: "); scanf("%d",&a); scanf("%d",&b); p1 = &a; p2 = &b; //交换前的信息 printf("p1 = %p\n",p1); printf("p2 = %p\n",p2); printf("*p1 = %d\n",*p1); printf("*p2 = %d\n",*p2); swap(p1,p2); //交换后的信息 printf("*p1 = %d\n",*p1); printf("*p2 = %d\n",*p2); return 0; }

通过上面的指针使用,我大致总结出了如下规律:
定义指针时一般为ptr;在使用时将ptr=&变量,相当于将变量的地址赋给指针变量ptr;而想要取地址的内容的时候使用ptr即可将该变量的值取出来。

指针形参的使用方法:

定义一个含有指针变量形参的函数;在主调函数中为该变量分配空间,并将一个指针变量指向该空间;以这个指针变量为实参调用定义好的函数;在函数内改变该指针指向的值;函数返回后,主调函数中的变量已经被改变。

2.指针型函数

函数的函数返回值也可以是指针型的数据,即地址。返回该类型值时,执行机制与返回其他类型完全相同。声明方式一般为:
数据类型 * 函数名(形参列表)

int * max(int a,int b,int c); //此max函数中的return语句必须返回一个变量的地址或一个指针变量的值。

范例6
使用指针变量作为函数返回值

#include int * max(int *a,int *b,int *c){ int *p = NULL; if(*a > *b)//如果a指向的值较大,将p设为a,否侧设为b p = a; else p = b; if(*p return a + b; } int main(void){ int (*fp)(const int a,const int b);//定义函数型指针 fp = add;//将其赋值为add printf("3 + 4 = %d\n",fp(3,4)); printf("3 + 4 = %d\n",(*fp)(3,4)); printf("%p\n",add); printf("%p\n",&add); printf("%p\n",fp); printf("%p\n",*fp); return 0; }


可以看出函数的地址即函数的值。

4.void型指针

void型指针就是无类型指针。void型指针可以指向存储任意数据类型端的空间。 定义形式如下:
void * 变量名;

void * p1 = "Goodbye"; int * p2 = p1; //可以赋值为任意类型的地址值,也可以用来作为赋值表达式的右值。

不能对void型指针做加减运算,因为对指针端的加减是基于指针指向的类型空间的字节长度进行的。void型指针变量的地址空间类型是未知的,因此不能进行加减。

**void型指针通常用作函数的形参。**声明如下:
void * 函数名(形参类型);

void *func(int a); int *p1 = NULL; int *p2 = NULL; ···· p1 = (int *)func(sizeof(int)); p2 = (char *)func(sizeof(int));

练习1

//使用指针从标准输入获取三个整数,并求其中最大值 #include int main(void){ int a,b,c; int *p1 = &a; int *p2 = &b; int *p3 = &c; scanf("%d %d %d",p1,p2,p3); if(*p1 p1 = p3; } printf("Max is %d\n",*p1); return 0; }


练习2

//实现一个可以初始化各种类型数据的函数 #include void initial(void *p,char type){ switch(type){ case 'i': //int型 *((int *)p) = 0; //强制类型转换为int型指针,并取内容 break; case 'c': //char型 *((char *)p) = '\0';//强制类型转换为char型指针,并取内容 break; case 'l': //long long型 *((long long *)p) /= 2;//强制类型转换为long long型指针,并取内容 break; case 'f': //double型 *((double *)p) = 0.0;//强制类型转换为double型指针,并取内容 break; case 'p': //指针类型 *((char **)p) = NULL;//强制类型转换为任意型指针,并取内容 break; default: //错误类型 printf("Error type!\n"); break; } }

四、指针与数组

指针也可以用来指向数组或指向数组中的一个元素。当指针指向数组时,可以通过指针来访问数组中的元素。

1.指向数组元素的指针

指向数组元素的指针是指一个指针变量,其指向的空间属于一个数组中的某一个元素。例如:

int array[10] = {0}; int *p0 = &array[0]; /*等效于*/ int *p0 = array;

这是因为数组的第0个元素的地址就是数组的地址。

2.指针访问数组

在数组中,相邻元素的地址只差一个元素的字长。因此,可以用一个数组元素型指针加上要访问的数组元素的地址偏移量即可获得该元素的地址。

范例8

#include #define SIZE 10 int main(void){ char a_ch[SIZE] = "Student"; int a_int[SIZE] = {1,2,3,4,5,6,7,8,9,10}; char *p_ch = a_ch; int *p_int = a_int; //使用索引法输出两个元素的地址 printf("&a_ch[4] = %p, &a_ch[3] = %p\n",&a_ch[4],&a_ch[3]); //使用索引法输出两个元素之间的地址偏移量 printf("&a_ch[4] - &a_ch[3] = %d\n\n",&a_ch[4]-&a_ch[3]); //使用索引法输出两个元素的地址 printf("&a_int[4] = %p, &a_int[3] = %p\n",&a_int[4],&a_int[3]); //使用索引法输出两个元素之间的地址偏移量 printf("&a_int[4] - &a_int[3] = %d\n\n",&a_int[4]-&a_int[3]); //使用指针法输出两个元素的地址 printf("p_ch + 4 = %p,p_ch + 3 = %p\n",p_ch+4,p_ch+3); //使用指针法输出两个元素之间的地址偏移量 printf("p_int + 4 = %p,p_int + 3 = %p\n",p_int+4,p_int+3); return 0; }


在计算两个地址的差的时候,为实际差/存储类型占字节大小

3.数组指针和数组变量

可以通过数组名和地址偏移量的组合来访问数组元素。

int array[5] = {1,2,3,4,5}; printf("%d\n",*(array+2));

数组元素型指针与数组变量在很多方面是不同的:

值的可改变性不同
数组元素型指针的值是可以改变的,而数组变量的值是不能改变的。//可行 int array_1[20] = {0}; int array_2[20] = {0}; int *p = array_1; p = array_2; //使指针指向另一个数组 p++; //使指针指向数组array_2[1] //不可行 array_1++; //试图使array_1的内容为array1[1],错误 array_1 = array_2; //错误 占用空间不同
数组元素型指针变量占用的空间是固定的,由系统决定。数组变量占用的空间是由程序决定的。访问方式不同
数组元素型指针访问方式为间接访问,而数组变量为直接访问。

范例9

#include #define SIZE 5 int main(void){ int a_int[SIZE] = {1,2}; int *p_int = a_int; //输出数组元素型指针和数组变量的占用空间 printf("sizeof(a_int) = %d\n",sizeof(a_int));//数组变量 printf("sizeof(p_int) = %d\n\n",sizeof(p_int));//指针变量 //输出数组第0个元素的值及其地址 printf("a_int[0] = [%d]\n",a_int[0]);//第0个元素 printf("&a_int[0] = [%p]\n\n",&a_int[0]);//第0个元素的地址 //输出数组变量的三个属性 printf("*a_int = [%d]\n",*a_int);//a_int指向的内容 printf("a_int = [%p]\n",a_int);//a_int的值 printf("&a_int = [%p]\n\n",&a_int);//a_int的地址 //输出数组元素型指针的三个属性 printf("*p_int = [%d]\n",*p_int);//p_int指向的内容 printf("p_int = [%p]\n",p_int);//p_int的值 printf("&p_int = [%p]\n",&p_int);//p_int的地址 return 0; }


从上面例子可以得到:数组变量的值=第2个元素的地址=数组变量的地址;而数组元素型指针的值=第1个元素的地址,但它的地址是另外一个地址。

4.数组指针作为函数形参

当数组作为函数形参时,数组的地址可以用作函数调用的实参。声明形式有以下几种:

//形式1 void print_array(int array[SIZE]); //形式2 void print_array(int array[]); //形式3 void print_array(int * array);

范例10

//验证上述3种方法都是指针变量 #include #define SIZE 6 void func_1(const char a[SIZE]){ printf("char a[SIZE]: %d\n",sizeof(a)); } void func_2(const char a[10000]){ printf("char a[10000]: %d\n",sizeof(a)); } void func_3(const char *a){ printf("char *a: %d\n",sizeof(a)); } void func_4(const char a[]){ printf("char a[]: %d\n",sizeof(a)); } int main(void){ char a_char[SIZE] = {'a','b','c','d','e','f'}; char * p_char =a_char; //定义字符指针 printf("sizeof(a):\n"); func_1(a_char); func_2(p_char); func_3(a_char); func_4(p_char); return 0; }


一般情况下,为了获得数组容量,通常将形式2和形式3改写为如下:

//形式2 void print_array(int array[],int size); //形式3 void print_array(int * array,int size);

这种方式还可以在函数开始时对数组容量进行判 断,提高程序安全。

5.调用含数组形参的函数

调用含数组形参的函数时,数组变量和数组元素型指针变量都可以作为实参。但不论是数组变量还是数组元素型指针变量,他们的值都是数组的首地址。

范例11
使用数组作为形参,实现了可输出升序数组中大于或等于0的数组元素的函数

#include #define SIZE 6 void print_array(const int a[],const int n){ //n为要输出数组元素的个数 int i = 0; for(i=0;i int a_int[SIZE] = {-55,-4,2,7,18,99}; //初始化一个升序序列 int *p = a_int; while(*p {1,2,3,4}, {11,12,13,14}, {21,22,23,24} }; //输出各种变量的字节长度 printf("sizeof(array) = %d\n",sizeof(array));//12 x 4 = 48 printf("sizeof(array[1]) = %d\n",sizeof(array[1]));//4 x 4 = 16 printf("sizeof(array[1][0]) = %d\n",sizeof(array[1][0]));//1 x 4 = 4 //输出数组首地址的各种形式的值 printf("&array\t\t = %p\n",&array); printf("array\t\t = %p\n",array); printf("&array[0]\t = %p\n",&array[0]); printf("&array[0][0]\t = %p\n",&array[0][0]); printf("*array\t\t = %p\n",*array); printf("array[0]\t = %p\n",array[0]); //输出数组中第1个一维数组首地址的各种表达形式的值 printf("*(array+1)\t = %p\n",*(array+1)); printf("&array[1]\t = %p\n",&array[1]); printf("&array[1][0]\t = %p\n",&array[1][0]); printf("*array+4\t = %p\n",*array+4); printf("array[1]\t = %p\n",array[1]); //比较*(array+1)和(*array+1)的不同 printf("*(array+1) - &array[0][0] = %d\n",*(array+1) - &array[0][0]); printf("(*array+1) - &array[0][0] = %d\n",(*array+1) - &array[0][0]); return 0; }

2.指针法访问二维数组

使用指针法访问二维数组有三种方式:使用指向数组元素的指针、使用一维数组型指针和使用二维数组型指针
1. 使用指向数组元素的指针

int a[3][2] = {{1,2},{11,12},{21,22}}; int *p = *a; //即&a[0][0] 这里是将数组第0个元素的地址赋值给指针变量,而不是将数组首地址赋值给指针变量。 由于3X2二维数组中第i行第j列元素可以表示为如下形式。 *(*a + i * 2 +j) 使用p代替*a,可得表达式如下: *(p + i * 2 + j)

2. 使用一维数组型指针
对于一维数组型指针,应该将其赋值为数组第0个一维数组的地址。

int a[3][2] = {{1,2},{11,12},{21,22}}; int (*p)[2] = a; //即&a[0] 由于a中第m行第n列元素的地址可以表示为如下形式: *(*(a+m)+n) 使用p来代替a,可得如下表达式: *(*(p+m)+n)

3. 使用二维数组型指针

int a[3][2] = {{1,2},{11,12},{21,22}}; int (*p)[3][2] = &a; 那么数组a中第m行第n列元素可以表示为: *(*(*p+m)+n) //表示第m个1维数组地址下的第n个偏量*(**p+2*m+n) //第m行n列元素相对第一个元素的偏移量为(2 * m + n)

3.二维数组形参

二维数组的地址可以用做函数的形参。在传递二维数组地址时,可以使用三种不同的指针作为形参来获得二维数组。

1. 数组元素型指针
将数组第一个元素的地址作为实参赋值给形参,在函数中使用该首地址和元素偏移量的组合来获得数组元素的地址,通过地址访问元素。

范例11

#include #define ROW_SIZE 3 #define COL_SIZE 3 void print_array(int *p,const int row,const int col){ int i,j; for(i=0;i int i,j; for(j=0;j int array[ROW_SIZE][COL_SIZE] = { {1,2,3}, {11,12,13}, {21,22,23} }; printf("Output the matrix:\n"); print_array(*array,ROW_SIZE,COL_SIZE);//以*array为第一个实参调用函数 printf("Reverse the matrix:\n"); reverse(&array[0][0],ROW_SIZE,COL_SIZE);//以&array[0][0]为第一个实参调用函数 return 0; }

2. 使用一维数组型指针
可以使用一维数组型指针作为形参来传递二维数组。

范例12

#include #define ROW_SIZE 3 #define COL_SIZE 3 void print_array(int (*p)[COL_SIZE],const int row){ /*数组型指针作为变量*/ int i,j; for(i=0;i int i,j; for(j=0;j int array[ROW_SIZE][COL_SIZE] = { {1,2,3}, {11,12,13}, {21,22,23} }; printf("Output the matrix:\n"); print_array(array,ROW_SIZE);//以array为实参调用函数 printf("Reverse the matrix:\n"); reverse(&array[0],ROW_SIZE);//以&array[0]为实参调用函数 return 0; }


3. 使用二维数组型指针

范例13

#include #define ROW_SIZE 3 #define COL_SIZE 3 void print_array(int (*p)[ROW_SIZE][COL_SIZE]){ int i,j; for(i=0;i int array[ROW_SIZE][COL_SIZE] = { {1,2,3}, {11,12,13}, {21,22,23} }; printf("Output the matrix:\n"); print_array(&array); return 0; }

六、指针与字符

1.字符指针

字符指针是指向字符型内存空间的指针变量,一般定义语句如下:

char *p = NULL; 访问字符数组:将字符指针赋值为字符数组的首地址即可实现对字符数组的访问。char str[] = "Hello,world!"; char *p = str; printf("%c",*(p+i)); //访问str中的某个元素 printf("%s",p); //打印整个字符数组 同时也可以使用p改变字符数组中的内容,例如: for(i=0;i //初始化2个字符数组 char str1[SIZE] = "\0"; char str2[SIZE] = "\0"; //定义两个字符指针指向这两个数组 char *p1 = str1; char *p2 = str2; printf("Please input the first string: "); gets(p1); printf("Please input the second string: "); gets(p2); while(*p1 == *p2 && '\0' != *p1){ ++p1; //将p1后移 ++p2; //将p2后移 } //根据p1和p2指向的值来判断比较结果 if(*p1 == *p2) printf("They are same.\n"); else if(*p1 > *p2) printf("The first one is larger than the second one.\n"); else printf("The first one is smaller than the second one.\n"); return 0; }


范例16
使用字符指针将一个字符串的内容复制到另一个字符串中。

#include #define SIZE 256 int main(void){ char str1[SIZE] = "\0"; char str2[SIZE] = "\0"; char *p1 = str1; char *p2 = str2; printf("Please input the source string:\n"); gets(p1); //由于两个字符数组大小相同,因此不用考虑字符溢出 while('\0' != *p1) *p2++ = *p1++; p2 = str2; printf("The destination string is:\n"); puts(p2); return 0; }


*号和++属于同一优先级,且方向都是从右向左的,*p++和 *(p++)作用相同。
在这里的话*p2++和 *p1++的作用是先p1++进行移位,然后在用 *取其中的内容,实现一位一位地复制。

3.字符指针数组

在使用字符指针数组时,如果不指定字符长度,会使每一个字符串的空间都至少扩大为最长字符串所占空间,这样会对资源产生浪费。因此可以使用字符指针数组,在数组中保存字符指针,每个指针指向特定的字符常量。

范例17
使用啊字符指针数组对字符串进行排序

#include #include #define NUM 5 int main(void){ char * p[NUM] = {NULL}; int i,j; char * tmp = NULL; //数组内的有效字符指针个数必须等于NUM p[0] = "Nelson Aldrich"; p[1] = "A. Piatt Andrew"; p[2] = "Frank Vanderlip"; p[3] = "Henry P.Davison"; p[4] = "Charles D.Norton"; //对数组中的字符串排序 for(i=0;i if(0 int count = 0; while(*p != '\0'){ //如果*p为字符终止符,则结束循环 ++p; //后移*p ++count; //有效字长加1 } return count; } int main(void){ char str[SIZE] = "\0"; puts("Please input a string:"); gets(str); //*p = str,是一种指针访问数组的方式 printf("The length of this string is %d.\n",my_strlen(str)); return 0; }


练习2
设计一个取子字符串的函数,函数声明包括以下信息:
源字符串、子字符串开始位置、子字符串字符个数

#include #define SIZE 256 char *my_substr(char *src, //源字符串 const int size_src, //源字符串空间大小 char *dest, //目的字符串 const int size_dest,//目的字符串空间大小 const int start, //子字符串开始位置 const int n) //子字符串字符个数 { char *tmp = dest; int count = 0; //检测各类异常 if(NULL == src || NULL == dest || start >= size_src || n > size_dest){ printf("Error in function my_substr.\n"); return NULL; //如果错误,返回 } //实现子字符串的复制 for(src += start,count = 0; count
收藏

上一篇C++和JAVA得区别?

下一篇ZZUIL题解1111

  • 人气文章
  • 最新文章
  • 下载排行榜
  • 热门排行榜