当前位置: 首页 > article >正文

C程序设计语言——指针

一. 指针概述

1. 了解内存空间

  • 内存中每个字节的编号就是我们常说的内存地址,是按一个字节接着一个字节的次序进行编址。

  • 0x 开头代表以十六进制来表示的意思。

  • 1个内存地址只存1个字节 (Byte)数据;
    在这里插入图片描述

  • 内存给数据类型地址分配如下:

      char:占一个字节分配一个地址;int: 占四个字节分配四个地址;
    
  • *的三种用法

  1. 乘法

  2. 定义指针变量

    int *p;//定义了一个名字叫p的变量,int *表示p只能存放int变量的地址
    
  3. 指针运算符
    &的意思是获取一个变量的地址,它的操作数必须是变量
    *的意思是读一个地址指向的内容
    这两者是一对逆运算符

     该运算符放在已经定义好的指针变量的前面,如果p是一个已经定义好的指针变量,则*p表示以p的内容为地址的变量
    

2. 指针热身举例1

#include <stdio.h>int main(void)
{int * p;	//p是变量的名字,int * 表示p变量存放的是int类型变量的地址 int i = 3;	p = &i;		//OK
//	p = i;		//error;因为类型不一致,p只能存放int类型变量的地址,不能存放int类型变量的值
//	p = 55;		//error;原因同上 return 0; 
} 

图示:
在这里插入图片描述

二. 指针的定义

地址

1. 内存单元的编号
2. 从零开始的非负整数
3. 范围:0~4G-1(FFFFFFFF)(原来内存条大小)注:内存的编号是唯一的,但是内存里面存储的内容可以充分,不同的解读含义是不同的,(或解读为地址,或解读为变量的内容)

在这里插入图片描述

指针是什么

  1. 指针就是地址,地址就是指针。确切来说,指针是带类型的地址

  2. 指针变量就是存放内存单元编号的变量,或者说指针变量就是存放地址的变量

  3. 指针和指针变量是两个不同的概念

  4. 但是要注意:通常我们叙述时会把指针变量简称为指针,实际上他们的含义不同

  5. 指针的本质就是一个操作受限的非负整数

  6. 对变量的访问都是通过地址进行的

  7. 指针变量也是变量,只不过它存放的不能是内存单元的内容,只能存放内存单元的地址

     指针不能乘除,只能相减。
    

定义指针变量的一般形式:类型名 * 指针变量名

类型名:表示指针变量所指向变量单元的数据类型(也就是表示确定几个字节为一个单元)
指针本身的大小就是8个(或4个)字节,有编译器所占位数决定
指针变量只存放的是其他类型变量的第一个字节的地址

例如:int * p;

  • 一个变量的指针的含义包括两个方面,一是以存储单元编号表示的地址(如编号为2000字节),一是它指向的存储单元的数据类型(如int、char、float)
  • 指向整型数据的指针类型表示为int * ,读作:指向int的指针

拿普通变量跟指针变量做比较:

char a;   // 定义一个变量a,用于保存char类型的数据;
char * b; // 定义一个指针变量b,用于保存一个内存地址,这个内存地址上的数据必须是char类型的。

对变量访问的两种形式

  • 直接访问:直接按照变量名进行访问
  • 间接访问:将变量i的地址存放在另一个变量中,然后通过该变量来找到变量i的地址,从而访问i变量。

在这里插入图片描述
将数值3送到变量中

  • 直接访问:i = 3
  • 间接访问:将3送到变量i_pointer所指向的单元(即变量i的存储单元)*i_pointer = 3

给指针变量进行赋值:

#include<stdio.h>
int main (){char a = 5;      // char 类型占一个字节; char * b = &a;    // “&”是取变量的地址,取出a在内存中的地址;// 赋值给b指针,此时b变量存储的就是a地址。printf("我是a变量的值:%d\n",*b);        // *b表示输出b里面存储的地址上的数据; // 证明b上存储的是a的地址;printf("我是a变量的地址:%p\n",&a);printf("我是b变量的值:%p\n",b);return 0;
}

结果:
在这里插入图片描述
通过图示来理解:
在这里插入图片描述
怎么样引用指针变量

1. 给指针变量赋值p = &a; //把a的地址赋给指针变量p2. 引用指针变量指向的变量printf("%d", *p); //以整数形式输出指针变量p所指向的变量的值3. 引用指针变量的值printf("%o", p); //以八进制形式输出指针变量p的值

指针类型的概念

我们知道char类型的数据只占一个字节,有很多类型是需要多个字节来存储的,像int类型的数据就需要四个字节来存储(根据平台不同,长度也有可能不一致)。

对于int类型的指针从当前字节(地址)开始共四个字节(地址)都是属于该变量的值, 而对于char类型则只表示当前字节(地址)。代码如下:

#include <stdio.h>int main(void)
{int a = 259;int * p1 = &a;char * p2 = (char *)&a; // 这里需要强制转换一下类型printf("*p1=%d, *p2=%d\n",*p1,*p2);//验证一下它们存储地址printf("&a = 0x%p\n",&a);printf("p1 = 0x%p\n",p1);printf("p2 = 0x%p\n",p2);
} /*
结果为: 
*p1=259, *p2=3
&a = 0x000000000062FE0C
p1 = 0x000000000062FE0C
p2 = 0x000000000062FE0C
*/ 

通过图示来理解:
在这里插入图片描述
解释:
因为计算机是使用二进制来表示数字的,上面(259)十进制转换二进制是 [1 00000011],由于一个int类型变量占用四个字节,8位二进制为一个字节,补齐高位的0后,
则为 [00000000 00000000 00000001 00000011],
每8位二进制(一个字节)换算为十进制,则 [0  0  1  3]。

但是内存地址中有个概念叫"大小端模式",就会有两种不同的排序:[0  0  1  3] or [3  1  0  0]。

三. 指针的重要性

  • 表示一些复杂的数据结构

      对做一些系统需要:把现实的问题先存储为数据->操作数据->输出数据数据结构研究的就是:数据存储和数据操作
    
  • 快速的传递数据,减少了内存的耗用

  • 使函数返回一个以上的值

    • 如何通过被调函数修改主调函数普通变量的值

        1. 实参必须为该普通变量的地址2. 形参必须为指针变量3. 在被调函数中通过 *形参名 = .... ;的方式就可以修改主调函数相关变量的值
      
  • 能直接访问硬件

  • 能够方便的处理字符串

  • 是理解面向对象语言中引用的基础

总结:指针是C语言的灵魂

四. 指针的分类

1. 基本类型指针【重点】

1.1 基本概念

int i=10;
int *p = &i; //等价于 int *p; p=&i;
  • 详解这两部操作:
  • 1)p存放了i的地址,所以我们说p指向了i
  • 2)p和i是完全不同的两个变量,修改其中的任何一个变量的值,都不改变另一个变量值
  • 3)p指向i,*p就是i变量本身,更形象的说所有出现 *p的地方都可以换成i,所有出现i的地方都可以换成 *p

总结:

  • 1)如果一个指针变量(假定为p)存放了某个普通变量(假定为i)的地址,那么我们就可以说:“p指向了i”,但p与i是两个不同的变量,修改p的值不影响i的值,修改i的值不影响p的值
  • 2)*p 等价于i,或者说 *p可以与i在任何地方互换
  • 3)如果一个指针变量指向了某个普通变量,则 *指针变量 就完全等价于该普通变量

注意:

  • 1)指针变量也是变量,只不过它存放的不能是内存单元的内容,只能存放内存单元的地址
  • 2)普通变量前面不能加 *
  • 3)常量和表达式前面不能加&
  1. 举例:
    #include <stdio.h>int main(void)
    {int * p;	//p是变量的名字,int * 表示p变量存放的是int类型变量的地址 //int * p;不表示定义了一个名字叫做*p的变量//int * p;应该这样理解:p是变量名,p变量的数据类型是int *类型//所谓int *类型就是存放int变量地址的类型 int i = 3;	int j;p = &i;/*1. p保存了i的地址,因此p指向i2. p不是i,i也不是p,更准确的说:修改p的值不影响i的值,修改i的值也不影响p的值 3. 如果一个指针变量指向了某个普通变量,则*指针变量就完全等同于普通变量例子:如果p是个指针变量,并且p存放了普通变量i的地址,则p指向普通变量i, *p,就完全等同于i,或者说:在所有出现*p的地方都可以替换成i,所有出现i的地方都可以替换成*p *p就是以p的内容为地址变量 */ 	j = *p;		//等价于j = i; printf("i = %d, j = %d\n", i, j); return 0; 
    } 
    

1.2 指针的常见错误

举例一:

#include <stdio.h>/*
程序没有语法错误,但是一旦运行就会崩溃
原因:*p代表以p的内容为地址的变量但是指针变量p里面存储了垃圾值那么P就是有指向的变量*p就是以p里面这个垃圾值为地址的单元,那么*p就是我们不知道的单元(非法单元)然后将i=5赋值给了一个不知道单元但是程序就分配了两块空间,p和i但是我们使用了别的单元,就会出现错误
总结:我们编程就是和内存打交道,我们系统给我们分配了多少空间,我们就只能使用这些空间,如果使用了其他不属于我们的空间系统就应该要报错,如果不报错说明这个可能是病毒(强行读取或者更改其他程序的内存空间)我们一般在编译器中出现这种情况的话,编译器直接就会强行终止我们的程序,用来保护我们的系统 
*/
int main(void)
{int * p;	//等价于int *p;也等价于int* p;int i = 5;*p = i;printf("%d\n", *p);return 0;	
} 

举例二:

#include <stdio.h>int main(void)
{int i = 5;int * p;int * q;p = &i;
//	*q = p;		//error 语法编译会出错,类型不一致
//	*q = *p;	//error 编译没有出错,但是q指向的是以垃圾值为地址的变量,所以*q不能被赋值
//	p = q;	/*error 编译没有出错,q是垃圾指,q赋值给p,p也是垃圾指,q可以是垃圾值, 因为q的空间是程序可以控制的,是属于本程序的,本程序可以读写里面的垃圾值 但是*q代表的是一个你不知道的单元,*q所代表的内存单元的控制权限没有分配给本程序,所以本程序没有权限,你不能读、写里面的内容 运行到printf语句就会出错 */printf("%d\n", *q);return 0;	
} 

1.3 经典指针程序_互换两个数字

#include <stdio.h>/*
不能完成互换功能
因为当主函数运行到exchange进入子函数,系统为子函数又分配了另外两个a,b存储空间
在子函数中交换了a,b的值,但是当子函数运行完,系统释放了子函数的存储空间,并且也没有返回任何值
当程序回到主函数,主函数的a,b的值任然不变,主函数运行完,系统才释放主函数的存储空间。 
*/
void exchange(int a, int b)
{int t;t = a;a = b;b = t;return;
}
int main(void)
{int a = 3;int b = 5;exchange(a, b);printf("a = %d, b = %d\n", a, b);return 0;	
} /*
通过以下程序可以互换两个数字
通过指针可以改变主函数两个变量的值
把实参&a和&b的值发送给指针变量p和q等价于:p = &a ;q = &b说明了p指向了a变量,b指向了b变量 
*/
#include <stdio.h>
void exchange(int * p, int * q)	//形参的名字是p和q,接受实参数据的是p和q
{int t;	//如果要互换*p和*q的值,则t必须定义为int,不能定义为int *类型 t = *p;	//p是int *,*q是int类型 *p = *q;*q = t;
}int main(void)
{int a = 3;int b = 5;exchange(&a, &b);printf("a = %d, b = %d\n", a, b);return 0;	
} 

图示理解:
在这里插入图片描述

2. 指针和数组

指针和一维数组

每个数组元素在内存中占用存储单元,他们都有相应的地址。指针变量既可以指向变量,也可以指向数组元素(把某一个元素地址放到一个指针变量中)

数组元素的指针就是数组元素的地址。

1. 一维数组名

定义:一维数组名是一个指针常量,它存放的是一维数组第一个元素的地址

它的值不能被改变
一维数组名指向的是数组的第一个元素

理解:a <<==>> &a[0]

C语言中数组名代表数组中首元素(即序号为0的元素)的地址

下面两个语句等价
p=&a[0]; //把a数组的首元素的地址赋给指针变量p
p=a;	//p的值是数组a首元素(a[0])的地址
//回忆数组
#include <stdio.h>int main(void)
{int a[5];	//a是数组名,5是数组元素,元素就是变量 a[0]~a[4]//int a[3][4];//3行4列 a[0][0]是第一个元素,a[i][j]是第i+1行,j+1列 int b[5];//a = b;	//error,因为a是一个常量,常量不能被赋值printf("%#X\n", &a[0]);	//以十六进制形式的输出a[0]的地址 printf("%#X\n", a);		//十六进制形式的输出a的地址return 0;	
} 
/* 
输出结果:两者结果相同 
---------
0X62FE00
0X62FE00
---------
*/ 
2. 下标和指针的关系

引用数组元素可以用下标法(a[3]),也可以用指针法(*(a+i)),即通过指向数组元素的指针找到所需的元素。

如果p是个指针变量,则p[i] 用于等价于 *(p+i)
a[ i ] <<==>> * ( a+i ) 他们两指向的是同一个数组元素

理解 a+2:因为a是一个地址,a+2 表示a这个地址加上2 乘以(a所指向的变量所占的字节数)

[] 实际上是变址运算符,即将a[i]按照a+i计算地址,然后找出此地址单元中的值

举例:

#include <stdio.h>/*
a[3]是第四个元素原因:a[3]等价于*(a+3),(a+3)是第四个元素地址,所以*(a+3)就是第四个元素 
*(a+3)等价于a[3]等价于pArr[3]等价于*(pArr+3)也就是说他们四个所代表的是同一个变量
*/ void f(int * pArr, int len)
{pArr[3] = 88;			//pArr[3]等价于*(pArr+3) //所以在f函数里面对pArr进行操作和在主函数里面对a进行操作本质上是一样的 
} int main(void)
{int a[6] = {1,2,3,4,5,6};printf("%d\n", a[3]);	//结果为:3f(a, 6); printf("%d\n", a[3]);	//结果为:88 printf("%d\n", &a[3]);	//结果为:6487564 含义是:a[3]代表的那个变量地址的十进制表示 printf("%d\n", &a[4]); 	//结果为:6487568 return 0;	
} 

图示理解:
在这里插入图片描述

//题目:在一个整型数组a,有10个元素,要求输出数组中的全部元素#include<stdio.h>
int main(void){int a[10];int i;int * p;printf("请输入10个元素:"); for(i=0; i<10; i++){scanf("%d", &a[i]);}//方法一:下标法:a[i]/*for(i=0; i<10; i++){printf("%d ", a[i]);}*///方法二:指针法:*(a+i)/*for(i=0; i<10; i++){printf("%d ",*(a+i));} *///方法三:用指针变量指向数组元素for(p=a; p<(a+10); p++){printf("%d ",*p);} return 0;
} 
3. 确定一个一维数组需要几个参数(或者说:如果一个函数要处理一个一维数组,则需要接受该数组的哪些信息)

需要两个参数:

  • 数组第一个元素的地址
  • 数组的长度

举例:

# include <stdio.h>/*
f函数可以输出任何一个一维数组的内容
因为数组可以存储任何值,所以没法用某一个值当做数组结束标记的 
*/ 
void f(int * pArr, int len)	//参数是首地址和长度
{int i;for(i=0; i<len; ++i)printf("%d ", *(pArr+i));//	*pArr *(pArr+1) *(pArr+2)printf("\n");	
} int main(void)
{int a[5] = {1,2,3,4,5};int b[6] = {6,7,8};f(a, 5);					//a是int *类型 f(b, 6);return 0;	
} 
4. 指针变量的运算

指针变量不能相加 不能相乘 也不能相除
如果两个指针变量指向的是同一块连续的空间中不同存储单元,则这两个指针变量才能相减

只有当指针变量指向数组元素时可以进行以下运算:

  • 加一个整数 p+1
  • 减一个整数 p-1
  • 自加运算 p++
  • 自减运算 p–

p+i 的值是p+i * (p所指向的变量所占字节数) 也就表示指针变量p指向下一个元素
p-i 的值是p-i *(p所指向的变量所占字节数)
p++ < == > p+1
p - - < == > p-1

举例:

#include <stdio.h>int main(void)
{int i = 5;int j = 10;int * p = &i;int * q = &j;//这时候输出q-p是没有意义的,因为系统分配i,j空间是不连续的 int a[5];p = &a[1];q = &a[4];printf("p和q所指向的单元相隔%d个单元\n", q-p); 	return 0;	
} 
5. 一个指针变量到底占几个字节
预备知识:
sizeof(数据类型)
功能:返回值就是该类型所占字节数
例子:sizeof(int) = 4, sizeof(char) = 1sizeof(变量名)
功能:返回值是该变量所占的字节数

假设p指向char类型变量(1个字节)
假设q指向int类型变量(4个字节)
假设r指向double类型变量(8个字节)
p、q、r本身所占的字节数是否一样

举例看看:

#include <stdio.h>/*
Dev-c中运行结果为8 8 8
说明指针变量p、q、r每个都是用8个字节来存储地址长度
原因是:Dev-c是一个64位的编译器 
64位说明地址线条数是64根,地址的范围就是:00000.....00000(64位)~11111.....11111(64位)
64bit等价于8Byte,所以每个指针变量大小为8个字节*/ 
int main(void)
{char ch = 'A';int i = 99;double x = 66.6;char * p = &ch;int * q = &i;double * r = &x;printf("%d %d %d\n", sizeof(p), sizeof(q), sizeof(r)); return 0;	
} 

图示理解:
在这里插入图片描述
在这里插入图片描述

总结:
p、q、r都只保存了变量第一个字节的地址,所以都指向了变量的第一个字节
但是*q、*r分别表示了i(占4个字节)和x(占8个字节)的本身,而不是第一个字节。
这时候就需要靠变量本身的数据类型来确定它所指向的变量占几个字节

一个指针变量无论它指向的变量占几个字节,该指针变量本身只占4个字节(或者8个字节)
一个变量的地址使用该变量首字节地址来表示	

指针和二维数组

3. 指针和函数

3.1 如何通过被调函数修改主调函数中普通变量的值

  1. 实参为相关变量的地址
  2. 形参为以该变量的类型为类型的指针变量
  3. 在被调函数中通过 *形参变量名 的方式就可以修改主调函数中变量的值
#include <stdio.h>
void f(int * p) //不是定义了一个名字叫做*p的形参,而是定义了一个形参名字叫p,类型是int *
{*p = 100;
}int main(void){int i = 9;f(&i);printf("i = %d\n", i);return 0;
}
输入:i=100

3.2 什么是函数指针

如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。

3.3 指针变量怎么定义

例如:
int(*p)(int, int);

这个语句就定义了一个指向函数的指针变量 p。首先它是一个指针变量,所以要有一个“*”,即(*p);
其次前面的 int 表示这个指针变量可以指向返回值类型为 int 型的函数;后面括号中的两个 int 表示这个指针变量可以指向有两个参数且都是 int 型的函数。
所以合起来这个语句的意思就是:定义了一个指针变量 p,该指针变量可以指向返回值类型为int类型,且有两个整型参数的函数。p的类型为 int( * )(int, int)

3.4 函数指针的定义方式为:

函数返回值类型 (* 指针变量名) (函数参数列表);

“函数返回值类型”表示该指针变量可以指向具有什么返回值类型的函数;
“函数参数列表”表示该指针变量可以指向具有什么参数列表的函数。这个参数列表中只需要写函数的参数类型即可。

我们看到,函数指针的定义就是将“函数声明”中的“函数名”改成“(*指针变量名)”。
但是这里需要注意的是:“(*指针变量名)”两端的括号不能省略,括号改变了运算符的优先级。
如果省略了括号,就不是定义函数指针而是一个函数声明了,即声明了一个返回值类型为指针型的函数。

3.5 如何用函数指针调用函数

举例1
int Func(int x);   /*声明一个函数*/
int (*p) (int x);  /*定义一个函数指针*/
p = Func;          /*将Func函数的首地址赋给指针变量p*/

赋值时函数 Func 不带括号,也不带参数。由于函数名 Func 代表函数的首地址,因此经过赋值以后,指针变量 p 就指向函数 Func() 代码的首地址了

举例2
# include <stdio.h>
int Max(int, int);  //函数声明
int main(void)
{int(*p)(int, int);  //定义一个函数指针int a, b, c;p = Max;  //把函数Max赋给指针变量p, 使p指向Max函数printf("please enter a and b:");scanf("%d%d", &a, &b);c = (*p)(a, b);  //通过函数指针调用Max函数printf("a = %d\nb = %d\nmax = %d\n", a, b, c);return 0;
}
int Max(int x, int y)  //定义Max函数
{int z;if (x > y){z = x;}else{z = y;}return z;
}
结果:
--------------------------------
please enter a and b:3 5
a = 3
b = 5
max = 5
--------------------------------
//题目:对输入的两个整数按大小顺序输出 #include <stdio.h>
void swap(int * p1, int * p2);
int main(void){int a,b;int * p1, * p2;printf("请输入两个整数:");scanf("%d,%d",&a, &b); //输入的格式要完全对应才行 p1 = &a;p2 = &b;if(a<b){swap(p1, p2);} printf("max = %d, min = %d\n", a,b);return 0;
}
/*方法二:可行
将指向两个整型变量的指针变量作为实参传递给swap函数的形参指针变量 
在swap函数中交换两个变量a,b的值,而p1和p2的值不变。 
*/ 
void swap(int * p1, int * p2){int temp; //不能写成int * temp 因为temp中的值不能确定,所以temp所指向的单元不可预见 temp = *p1;*p1 = *p2;*p2 = temp;
}/*方法一:可行 
不交换整型变量的值,而是交换两个指针变量的值(即a和b的地址) 
if(a<b){int * p;p = p1;p1 = p2;p2 = p;
} 
printf("max = %d, min = %d\n", *p1,*p2);
*/ /*方法三:不可行
不能通过改变指针形参的值而使指针实参的值改变 
void swap(int * p1, int * p2){ //形参是指针变量 int * p;	//下面三行交换的是p1和p2的指向 p = p1;p1 = p2;p2 = p;
} 
C语言中实参变量和形参变量之间的数据传递是单向的值传递,用指针变量作函数参数时
同样是这样的,不可能执行被调函数来改变实参指针变量的值,但可以改变实参指针变量所指变量的值 
*/ 

函数调用只可以得到一个返回值(即函数值),而使用指针变量作为参数,可以得到多个变化了的值。

3.6 用数组名作函数参数

当用数组名作参数时,如果形参数组中各元素的值发生改变,实参数组元素的值随之变化。

  • swap(a[1],a[2]); 用数组元素a[1],a[2]作实参的情况,与用变量作实参的情况是一样的,是值传递方式,进行单向传递,当形参中的x,y改变时。实参中的a[1]、a[2]的值并不改变。
  • 用数组名作为函数参数的情况,实参数组名代表该数组首元素的地址,而形参是用来接收从实参传递过来的数组首元素地址的。所以形参是一个指针变量。C编译都是将形参数组名作为指针变量来处理的
  • fun(int arr[], int n) <==> fun(int * arr, int n)
//题目:将数组a中n个整数按相反顺序存放#include<stdio.h> 
int main(void){void inversion(int x[], int n);int i,a[10]={2,3,4,1,2,4,12,3,4,9};printf("原始数组:");for(i=0;i<10;i++){printf("%d ",a[i]);}printf("\n");inversion(a,10);printf("反转之后的数组:");for(i=0;i<10;i++){printf("%d ",a[i]);} return 0;
} /* 方法一:实参用数组名a,形参用数组名 
void inversion(int x[], int n){int temp, i, j, m=(n-1)/2;for(i=0;i<=m;i++){j=n-1-i;temp = x[i];x[i] = x[j];x[j] = temp; }
}
*//* 方法二:实参用数组名a,形参用指针变量名 
void inversion(int * x, int n){int temp, m=(n-1)/2;int * p, * i, * j;i=x; j=x+n-1; p=x+m; for(;i<=p;i++,j--){temp = *i;*i = *j;*j = temp; }
}
*/

如果有一个实参数组,要想在函数中改变此数组中的元素的值,实参与形参的对应关系有以下4种情况。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4. 指针和字符串

/*
判断是否到达字符串的结尾,即判断当前字符是否为'\0'遍历字符串s,使用整型变量n存放下标,那么判断当前字符是否为'\0'使用while(s[n]!='\0')或while(*p!='\0')对字符串操作结束后,添加'\0'下标n为字符串中最后一个字符的下标,要添加结束标识,表示为:s[n++]='\0'或指针p指向最后一个字符,*(p++)='\0'
*/
#include <stdio.h>
#include <string.h>
int main(void)
{char a[5][10]={"China","beijing","very","Welcome","you"};char *p[5];int i,len;for(i=0;i<5;i++){p[i]=*(a+i);len=strlen(p[i]);printf("%s", p[i]+len-1);printf("%c",*(p[i]+len-1)); 	}return 0;} 输出:aaggyyeeuu

5. 指针和结构体

6. 多级指针

  • 二级指针:指针的指针

    char a = 5;    
    char * p1 = &a;
    char ** p2= &p1;
    printf("*p=%d,**p2=%d\n",*p1,**p2);   // 输出:*p1=5,**p2=5
    

    图示理解:
    在这里插入图片描述

  • 多级指针举例1:

    #include <stdio.h>int main(void)
    {int i = 10;int * p = &i;int ** q = &p;int *** r = &q;//r = &p; error 因为r是int ***类型,r只能存放int **类型的变量的地址printf("i = %d\n", ***r); return 0;
    }
    
  • 多级指针图示理解:
    在这里插入图片描述

  • 多级指针举例2:

    #include <stdio.h>void f(int ** q)
    {//*q就是p 
    }void g()
    {int i = 10;int *p = &i;f(&p);	//p是int *类型,&p是int **类型	
    }int main(void)
    {g();return 0;	
    } 
    

五. 指针的缺点

1. 导致内存泄漏(内存就会越用越少)

2. 产生野指针

在这里插入图片描述

free(p):把p指向的空间释放这时候就不能再free(q);因为p,q,r指向的是一个空间,只需要释放一次。如果再次释放同一个空间程序会出错当程序特别大的时候,无法检测有哪个空间没有被free。如果出现对用过空间一次都没有free的话,会出现内存泄漏这个是C语言程序的致命缺点,无法去避免
http://www.lryc.cn/news/2420333.html

相关文章:

  • 【iReport+JasperReport】1.iReport与JasperReport基础
  • sql中的int、bigint、smallint和tinyint四种数据类型
  • 【大数据】Hadoop—— 三大核心组件理论入门 | 完全分布式集群搭建 | 入门项目实战
  • ansible脚本-Playbook(一)
  • mdf文件和ldf文件ndf是什么,怎么用?如何给SQL server添加数据文件?分离和附加数据库的操作
  • Ctex+texmaker
  • caffe入门教程
  • oninput、onblur和onchange的区别
  • MySQL常用命令大全(完整)
  • datatables实现数据的展示
  • IP协议及ICMP协议简介
  • Vue...el和data的两种写法
  • SwiftUI中Stack、Spacer与Divider的使用
  • PL/SQL 数据类型与基本输出
  • 转载:DSN服务器学习
  • 基于laser的Google---cartographer建图测试总结(实测可行)
  • tpl模板引擎和使用
  • 手把手教你爬取天堂网1920*1080大图片(批量下载)
  • Unity动画☀️六、Humanoid和Generic的区别、导入方式(骨骼映射、Avatar创建)
  • 因子(Number_Of_Factors)
  • 再生龙clonezilla安装新设备全过程
  • 【Haskell】一个没有循环的世界
  • 目标检测之空间变形网络(STN)
  • 什么是ISO(国际标准化组织)?
  • 简单介绍了解白鹭引擎Egret
  • CSharp编程语言
  • 如何在linux系统下安装QQ
  • 【MySQL管理】:用户User和权限Privileges
  • Oracle Rac 介绍
  • HTML基础-06-表格(表<table> ,行 <tr>,列 <tb>,表头 <th>,跨列colspan,跨行rowspan,单元格边距 cellpadding,单元格间距cellspacing)