C语言的指针学习笔记

目录

# 指针的定义

# 指针的类型

# 指针的基本语法和应用

        · 指针变量

        · 引用指针

                (1)赋值

                 (2)引用指针所指变量

        · 指针的加减运算

                (1)指针 ± 整数 :

                (2)同类型指针减法运算

        · 指针变量参与函数

                (1)函数声明

                (2)返回指针

# 指针引用数组

        · 定义数组指针: 

         · 数组元素指针的算数运算

          · 指针引用数组元素

                · 指针引用多维数组

# 指针引用字符串

        · 字符串的引用方式

        · 使用字符指针变量 与 字符数组的区别

# 指向函数的指针

        · 什么是函数的指针

        · 用函数指针变量调用函数

        · 嵌套函数

# 指针数组

        · 什么是指针数组

        · 语法

# 多重指针

        · 语法

        · 运用 

        · 多重指针

# 声明顺序(优先级)


# 指针的定义

        何为指针?

        简而言之,指针是一个指向性的地址

        例如:现在定义了一个整型变量a,编译系统则会分配4个字节的空间,此时这四个字节有一个编号(带类型的),这个编号就是变量a的地址 ¹

        当对变量a进行赋值操作时,它的地址并不会改变,改变的是空间里的所储存的数据,此时通过指针,也就是“地址”,就能锁定到这块空间,从而访问 ² 到“地址”里面的存储的数据,进行运算或者函数。

        ¹ C语言中的地址与我们所直观认为的有所区别,一串地址例如地址1010,我们并不能直接得到该单元的数据,因为缺少了数据类型。此时虽然锁定了指定的储存单元,但是无法确定是从一个字节中(字符数据)提取信息,还是两个字节(短整型),或者是四个字节(整型)。因此,C语言中所说的指针,不仅包含了地址信息,也包含了它所指向的数据的类型信息。

        一个完整的指针包含地址和其类型。

        ² 对变量的访问:分为直接访问和间接访问。

        其中直接访问是直接按变量名进行访问,譬如语句:

a=b+c;

         就是直接取出b和c中的值,将其相加后,将结果输送到a所在的单元之中。

        间接访问是指定义一个特殊的变量用于储存地址(指针变量),通过取出地址然后找到地址所代表的单元,从中取出变量的信息。

        通过这种方法,可以避免改变变量本来存储的信息。

        简而言之,指针变量的值是地址/指针本身


# 指针的类型

        与变量的数据类型相似,指针也分为整型指针/浮点指针等

         具体有大致如下: 

//声明指针
char  *pc = NULL;    //字符指针 
int   *pi = NULL;    //整形指针 
short *ps =NULL;     //短整型指针 
long  *pl = NULL;    //长整型指针 
float *pf = NULL;    //单精度浮点型指针
double *pd = NULL;   //双精度浮点型指针 
……

#NULL为空指针# 


# 指针的基本语法和应用

        · 指针变量

为了能够更好的访问内存空间,我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量。

定义指针变量的语法为:类型名  *  指针变量名


 int num = 10;        //在内存中开辟一块空间
 int *p = #       //这里我们对变量a,取出它的地址,可以使用&操作符。
                    /* num变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
	                中,p就是一个之指针变量。*/

* 指针的定义语法与变量的定义语法类似,但指针尽量初始化(否则容易使定义的指针变为“野指针” ¹ 

        · 引用指针
                (1)赋值
p=&a;    //把a的地址赋给指针变量p

                指针变量p的值是变量a的地址,p指向a

* 取地址符 & ,&a是变量a的地址

                 (2)引用指针所指变量
int *p = &a ;
*p = 1 ;

                表示:将1赋值给指针p所指向的变量(即变量a),相当于“a=1;” 

        · 指针的加减运算
                (1)指针 ± 整数 :

char* 指针 + 1,意思是跳过一个字符,也就是向后走1个字节
short* 指针 + 1,向后走2个字节
int* 指针 + 1,意思是跳过1个整形,也就是向后走4个字节
double* 指针 + 1,意思是跳过一个double,也就是向后走8个字节
……

                 指针类型决定了指针的步长(向前 / 向后 跳过多少字节)

                (2)同类型指针减法运算

                指针类型与指针类型相减后,表示两个指针之间元素个数。

* 注意 指针+指针 没有意义

* 两个指针相减的前提是:指针指向的同一块连续的空间

        · 指针变量参与函数
                (1)函数声明
void function ( int * p1,int * p2) ;

                * 对 function 函数的声明,需求两个整形指针变量p1,p2

                (2)返回指针
int * function() {
	int num = 100 ;
	return & num ;     
}

                * 函数function返回一个指针变量

¹  野指针

        野指针就是指针指向的位置是未知的(随机的、不正确的、没有明确限制的)

        其带来的后果就是指针指向了一个未知 的地址,难以运用指针进行进一步赋值和运算,因此代码中要尽量避免野指针的产生。

                i.野指针的产生原因:

int *p ;        //局部变量指针未初始化,默认为随机值

* 指针没有初始化,默认随机 

    int arr[10] = {0};
    int *p = arr ;
    for ( int i = 0 ; i <= 11 ; i++ ){      //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
   }

* 指针的越界访问(例如指针指向范围超过数组的范围,指针p此时变为野指针) 

int * function()
{
	int x = 1 ;
	return & x ;            //return函数后,x内存回归操作系统

int main() {
	int * p = function() ;
	*p = 200 ;
	return 0 ;
}

* 指针所指向的空间被释放后,指针变为野指针

                ii.规避野指针的产生

  1. 指针初始化(已知指向时明确初始化 / 未知初始化为NULL);
  2. 注意指针范围,防止越界;
  3. 指针指向空间释放,及时置NULL;
  4. 避免返回局部变量的地址;
  5. 指针使用之前检查 有效性 

           //  检查指针的有效性:

//检查指针的有效性
int main() {
    int *p = NULL ;        //未知指向初始化为NULL
    int a = 10;
    p = & a;               //明确初始化
    if (p != NULL){        //为空指针不访问(无效指针)
        *p = 20 ;          //不为空再访问
   }
    return 0; 
}

原理:空指针 NULL 位于内核区域不能直接访问。


# 指针引用数组

        · 定义数组指针: 

在定义数组元素的指针时,通过下面语句对其进行初始化:

int * p = & a[0] ;

其作用是将数组首元素 a[0] 的地址赋给指针变量 p 

         · 数组元素指针的算数运算

与指针的运算相类似,当指针已经指向一个数组元素时,可以对指针进行下列运算:

加一个整数(如 p+1);        // 表示指针指向同一数组的下一个元素

减一个整数(如 p -1);        // 表示指针指向同一数组的上一个元素

类似的也有自加和自减( p++ p-- ++p --p )

两个指针相减 p1-p2 ;           // 表示之间有多少个元素

*指针相加无意义

          · 指针引用数组元素

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

对于一个给定的数组,引用起元素既可以使用下标法(如 a [5] ),也可以使用指针法

//下面两行代码等价

p = & a[0] ;    //p的值是 a[0] 的地址

p = a ;         //p的值是数组 a 的首元素(即 a[0])的地址

通过两种方法遍历数组

//下标表示

for ( i = 0 ; i < 10 ; i++ ){
    scanf ( " %d ", & a[i] ) ;
}
for ( i = 0 ; i < 10 ; i++ ){
    printf ( " %d ", a[i] ) ;
}



//指针表示

int * p , i ;

for ( i = 0 ; i < 10 ; i++ ){
    scanf ( " %d ", & a[i] ) ;
}
for ( p = a ; p < ( a+10 ) ; p++ ){
    printf ( " %d ", * p ) ;
}

 语句: * ( arr + i ) 与 arr [ i ] 无条件等价

                · 指针引用多维数组

                        二维数组的地址

        定义二维数组 arr [ i ][ j ] ,其中元素地址可以描绘为 & [ i ][ j ] 。

        由上文介绍的等价语句,地址同时可以写为如下:

        a [ i ][ j ] == * ( a[ i ] + j ) == * ( * ( a + i ) + j )  .

        即表示为:a [ i ][ j ] 与  * ( * ( a + i ) + j ) 等价。

 示例:

#include <stdio.h>

int main() {
	int a[3][4] = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24};
	printf("%d,%d\n", a, *a);						//0行起始地址 与 0行0列元素地址
	printf("%d,%d\n", a[0], *(a + 0));				//0行0列元素地址
	printf("%d,%d\n", &a[0], &a[0][0]);				//0行起始地址 与 0行0列元素地址
	printf("%d,%d\n", a[1], a + 1);					//1行0列元素地址 与 1行起始地址
	printf("%d,%d\n", &a[1][0], *(a + 1) + 0);		//1行0列元素地址
	printf("%d,%d\n", a[2], *a + 2);				//2行0列元素地址
	printf("%d,%d\n", &a[2], a + 2);				//2行起始地址
	
    printf("%d,%d\n", a[1][2], *(*(a + 1) + 2));	//1行2列元素的值
	printf("%d,%d\n", *a[2], *(*(a + 2) + 0));		//2行0列元素的值
	return 0;
}

运行结果:

6422000,6422000
6422000,6422000
6422000,6422000
6422016,6422016
6422016,6422016
6422032,6422008
6422032,6422032
14,14
18,18

 输出结果中每一行两个相同,意味着两处代码等价;


# 指针引用字符串

        · 字符串的引用方式

方式一:

用字符数组存放一个字符串,可以通过数组名+下标引用其中一个字符,也可以用数组名+“ %s ”声明输出。

char string [] = "C Primer Plus" ;

printf (" %s \n ",string ) ;
printf (" %s \n ",string [5] ) ;

字符数组的存储方式如下:

CPrimerPlus\0

 如代码 string[5] ,等价于 *(string + 5)。

string+5 是一个地址,它指向了字符 “ i ” 。

 方式二:

用字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量。

char * string = "C Primer Plus" ;

printf (" %s \n ",string ) ;

其中,将字符串的第一个元素的地址 赋给指针变量 string ,使 string 指向字符串第一个字符,也称为指针指向了字符串。

此时对指针变量进行再赋值,string 指向新的字符串,无法再引用原来的字符串。

        · 使用字符指针变量 与 字符数组的区别

(1)字符数组由元素组成,每个元素一个字符,字符指针变量中存放地址;

(2)允许对字符指针变量赋值,但不能对数组名赋值;

(3)对于字符串数组,初始化是将字符赋给各个元素;对于指针变量,初始化是将第一个字符的地址赋给指针变量;

(4)指针变量的值可以改变,字符数组名代表了其首元素地址,不能改变;

(5)字符数组中的各个元素可以被更改,但字符指针变量所指向的字符串变量中的内容不可取代,不能被再赋值;

(6)用指针变量指向一个格式字符串,可以用它代替 printf 函数中的格式字符串。


# 指向函数的指针

        · 什么是函数的指针

        如果定义了一个函数,该函数会存在一个“入口”,存在一个入口地址,每次调用函数时,从函数名得到函数的起始地址,并执行函数代码。

        换而言之,函数名就是函数的指针,他代表函数的起始地址。

        因此,如果定义了一个指针p,它指向该函数的起始地址,也就指向了该函数。

        · 用函数指针变量调用函数

        语法:  类型名 ( * 指针 )( 函数参数列表 )

                例如:int ( * p ) ( int a , int b )

//示例
int function (int a , int b );

int c ;
c = ( * p ) ( a , b ) ;
c = function ( a , b ) ;

                *其中两次函数调用等价 

        · 嵌套函数

        用指向函数的指针作为函数参数

//示例
void function ( int ( * x1 ) ( int ) , int ( * x2 ) ( int , int ) ) ;

//嵌套函数的声明

作用 / 好处 :在多次调用 function 函数的时候,若每次内部调用的函数都不相同,这时使用指针变量就显得更加方便了,原因是在每次调用function的时候只需要给出不同的函数名作为此次调用的实参即可,而function函数本身没有进行任何修改。

示例:输入整数a,b;由用户输入1/2/3,输入1执行取大,输入2执行取小,输入3执行求和.

#include <stdio.h>

int main() {
	int function(int x, int y, int (*p)(int, int));	//function选择函数声明
	int max(int, int);								//取大函数声明
	int min(int, int);								//取小函数声明
	int sum(int, int);								//求和函数声明
	int a, b, n;
	scanf("%d %d %d", &a, &b, &n);

	if (n == 1)
		function(a, b, max);				//执行取大函数
	else if (n == 2)
		function(a, b, min);				//执行取小函数
	else if (n == 3)
		function(a, b, sum);				//执行求和函数
	return 0;
}

int function(int x, int y, int (*p)(int, int)) {
	int res;
	res = (*p)(x, y);
	printf("%d", res);
}									//定义function

int max(int x, int y) {
	int z;
	if (x > y)
		z = x;
	else
		z = y;
	printf("max=");
	return (z);
}									//定义取大函数

int min(int x, int y) {
	int z;
	if (x < y)
		z = x;
	else
		z = y;
	printf("min=");
	return (z);
}									//定义取小函数

int sum(int x, int y) {
	int z;
	z = x + y;
	printf("sum=");
	return (z);
}									//定义求和函数


# 指针数组

        · 什么是指针数组

一个数组,若其中元素都是指针类型的数据,就称为指针数组,相当于每一个元素都是一个地址。

*区分 指针数组 与 引用数组的指针 :

前者元素是地址,后者只是用外层指针存储了第一个元素的地址

        · 语法

                类型名 * 数组名 [ 数组长度 ]

//示例
char * name [ ]

 * 注意区分

指针数组   与   指向一维数组的指针

int * p [ 4 ]        int ( * p ) [ 4 ]


# 多重指针

简而言之是一个指针指向了另外一个指针

即指针指向的变量同时也是一个地址指向另一个变量

        · 语法
//示例
char ** p
        · 运用 
    int a = 1;
	int *pa = &a ;          // pa 就是指针变量,一级指针变量,表示指针指向的 a 是 int
	int* *ppa = & pa ;      // ppa 就二级指针,表示 pp 指向的 p 的类型是 int*
        · 多重指针

                多指几次,俗称 套娃 

比如:五⭐程序员(整活代码)

#include <stdio.h>

int main() {
	int n = 123;                        // int
	int *oneStar = &n;                  // int *
	int **twoStar = &oneStar;           // int **
	int ***threeStar = &twoStar;        // int ***
	int ****fourStar = &threeStar;      // int ****
	int *****fiveStar = &fourStar;      // int *****

	printf("n = %d", ***** fiveStar); // 五次取值,还原为int
	return 0;
}

最终输出结果经过层层解引用,输出  n=123


# 声明顺序(优先级)

对于数组、函数、指针,它们遵循下列的运算数序,优先级从高到低:

1、括号( )

2、函数声明的 ( ) 与 数组声明的( )优先级相同

3、指针 声明的 *

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值