初识C语言——指针

目录


指针
野指针
二级指针

指针进阶

  1. 字符指针

  2. 数组指针

  3. 指针数组

  4. 数组传参和指针传参

  5. 函数指针

  6. 函数指针数组

  7. 指向函数指针数组的指针

  8. 回调函数

  9. 相关问题

  • 指针是用来存放地址的,地址是唯一标识一块地址空间的。
  • 指针的大小,或者说是指针存放的地址的大小在32位平台是4个字节,在64位平台是8个字节。

&a的意义
int main()
{
    int a=10;//a占4个字节。
    int *pa = &a;//拿到的a的4四个字节中第一个字节的地址。
    return 0;
}

指针不同类型是有意义的。

int main()
{
char * pa;//+1的化变化一个字节的空间。
int * pb;//+1的话变化4个字节的空间。
float * pd;//+1的话变化4个字节的空间。

return 0;
}

指针类型的意义

  1. 指针类型决定了:指针解引用的权限有多大。
  2. 指针类型决定了,指针走一步,能走多远(步长);

不会创建一种通用类型的指针:

访问类型发生了变化,访问权限也发生了变化。

int * pa;
char* pc;
float* pf;
printf("%d\n",sizeof((pa));//4
printf("%d\n",sizeof((pc));//4
printf("%d\n",sizeof((pf));//4

在调试过程中,可以看到窗口内存。

int main()
{
    int arr[10] ={0};
  
    int *p = arr;
    char *pc = arr;
    
    printf("%p\n",p);   //004ffc40
    printf("%p\n",p+1); //004ffc44
    
    printf("%p\n",pc);  //004ffc40
    printf("%p\n",pc+1);//004ffc41
    
    return 0;
}

野指针
int *test()
{
    int a = 10;//如果a的变量类型是static int类型的话,就可以不报错。
    return &a;
}

int main()
{
    int *p = test();
    *p = 20;//返回的a的地址被销毁了。
    
    return 0;
}//编写错误。
int main()
{
    //p就是一个野指针
    int* p;//p是一个局部的指针变量,局部变量不初始化的化,是随机值。
    //补充:全局变量会自动初始化成0.
    *p = 20//非法访问内存。
    
    int arr[10] = {0};
    int *p = arr;
    int i = 0;
    for(i = 0;i<=10;i++)
    {
        *p = i;
        p++;
    }//for循环要循环的次数要超过数组的大小。
   return 0;
}

规避野指针

  1. 指针初始化,置成NULL
  2. 小心指针越界
  3. 指针指向空间释放置成NULL
  4. 指针使用之前检查有效性。
int main()//1.指针初始化
{
    int *p = NULL;//方法一置成NULL;
    
    int a = 10;
    int * ptr = &a;//方法二:明确初始化的值。
}
int main()
{
    //C语言本身不会检查数据的越界行为的。
    int *p = NULL;
    
    if( p !=NULL)
        *p = 10;
    return 0;//?
}

    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int* p = arr;
    int* pend = arr + 9;
    while(p<=pend)
    {
        printf("%d\n",*p);
        p++;
    }
    return 0;
//会将数组依次打印出来。

指针的±和指针的关系运算

#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数:指针的关系运算
for(vp = &values[0];vp<&values[N_VALUES];)//指针的关系运算!
{
    *vp++ = 0;
}//指针+整数

指针允许数组元素的指针与数组最后一个元素后面的那个内存位置的指针比较,但不允许与指向第一个元素之前的那个内存位置的指针进行比较。

//实现了strlen的逻辑运算。
int my_strlen(char* str)
{
    char* start = str;
    while(*str != '\0')
    {
        str++;
    }
    return str - start;
}
//指针地址相减
//得到数组中中间元素的个数。
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    printf("%d\n", &arr[9] - &arr[0]);//**9**
            //最后元素的下标,第一个元素的下标
    return 0;
}
//指针和指针相减的前提:两个指针指向同一块空间。

指针没有加,没有指针和指针的加。

就像星期几之间的加减。

没有被解引用,就不会越界。!


//数组取地址的两种表示
int main()
{
    int arr[10] = {0};
    int *p = arr;
    int i = 0;
    for (i = 0;i<10;i++)
    {
        printf("%p == %p\n",&arr[i],p+i);
    }
    
    return 0;
}

二级指针
int main()
{
    int a = 10;
    int* pa = &a;//pa是指针变量,一级指针
    
    //ppa就是一个二级指针变量
    int **ppa = &pa;//pa也是个变量,&pa取出pa在内存中的起始地址。
    return 0;
}

指针进阶

前情提要:

  1. 指针就是变量,用来存放地址,地址唯一标识一块内存空间。//err指针是指针变量,传递的是变量。
  2. 指针的大小是固定的 4/8 个字节(32位平台/64位平台)
  3. 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
  4. 指针的运算。
字符指针
 char* p = "hello world";
    printf("%c\n", *p);//h
    printf("%c\n", *(p+1));//e
    printf("%s\n", p);//hello world
    printf("%c\n", p);//?
int main()
{
    //第一个,本质上是把"hello world"这个字符串的首元素的地址存放在ps中。
    char* ps = "hello world";//12个字符。
    char arr[] = "hello world";
    
    printf("%c\n",*ps);//h   ->存放一个 h 在里面。 
    printf("%c\n",arr);//? 虽然不报错,但打印无意义
    printf("%s\n",arr);//打印完整的字符串。
    
    printf("%s\n",ps);//hello world
    printf("%s\n",arr);//hello world
    
   //指针存储的是常量字符串
    *ps = 'w';//no      
    //数组存储的是可变的字符串
    arr[0] = 'w';//yes
    
    return 0;
}

int main()
{
    char str1[] = "hello bit";
    char str2[] = "hello bit";
    
    const char* str3 = "hello bit";
    //这个是常量字符串,不能被更改。     只有一份,
    const char* str4 = "hello bit";
    //对于相同的常量字符串,不同的变量调用的是同一块地址的数据。
    
    if(str1 == str2)
        printf("str1 and str2 are same\n");
    else
        printf("str1 and str2 are not same\n");//yes
    if(str3 == str4)
        printf("str3 and str4 are same\n");//yes
    else
        printf("str3 and str4 are not same\n");
    
    return 0;
}

指针数组
//指针数组
//本质上是数组 - 数组中存放的是指针(地址)
//int* arr[3];//存放整型指针的数组。
int arr[10];//整型数组 - 存放整型的数组就是整型数组
char ch[10];//字符数组 - 存放的是字符
//指针数组 - 存放指针的数组
int* parr[5];//整型指针的数组

    //指针数组定义
    //比较挫
    int a = 10;
    int b = 20;
    int c = 30;
    int* arr[3] = {&a,&b,&c};
    int i = 0;
    
    for(i = 0; i<3;i++)
    {
        printf("%d ",*(arr[i]));
    }
    // 10 20 30 
  • 指针数组的打印形式
int main()
{
    int a[5] = {1,2,3,4,5};
    int b[] = {2,3,4,5,6};
    int c[] = {3,4,5,6,7};
    
    int* arr[3] = {a,b,c};
    int i = 0;
    
    for(i = 0;i<3;i++)
    {
        int j = 0;
        for(j=0;j<5;j++)
        {
            //两种打印的形式
            printf("%d ", *(arr[i] + j));
            printf("%d ", arr[i][j]);
            //可以通过二维数组的形式,这样打印,但它不是二维数组,从内存的角度上看。
        }
    }
    return 0;
}

数据的存放、取出、打印,用一种类型处理的。


数组指针

本质上,是一种指针。

int main()
{
    int a = 10;
    int* pa = &a;//整型指针
    
    char ch = 'w';
    char *pc = &ch;//字符指针
    
    /***/
    int arr[10] = {1,2,3,4,5,6};
    int *(*parr)[10] = &arr;//数组指针 - 取出的是数组的地址。
    //arr - 数组名是首元素的地址 - arr[0]的地址。
    
    double* d[5]; //存放double*类型的数组。 
    double* (*pd)[5] = &d;//pd是数组指针。
    return 0;
}

    int arr[10] = {0};
    
    printf("%p\n",arr);//首元素地址
    printf("%p\n",&arr);//数组地址
    //两者的打印的值  相同,表示的意义不同
    //就像
    char c = 'a'; //ASDII 值  97 -char
    int i = 97; //       也是97  -int   类型不同
    char c = 97;//使用%c打印的结果也是a。

指针+1

int main()
{
    int arr[10]={0};
    int *p1 = arr;//指针数组
    int (*p2)[10] = &arr;//数组指针
    
    
    printf("%p\n",p1);
    printf("%p\n",p1+1);//变化4个字节
    
    printf("%p\n",p2);
    printf("%p\n",p2+1);//变化40个字节,十进制
    
    return 0;
}
  • 数组指针解引用,再解引用。
int main ()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    
    int (*pa)[10] = &arr;
    int i = 0;
    for(i = 0;i<10;i++)
    {
        //*pa是数组指针,不是解引用之后的内容。
        printf("%d ",*((*pa)+i));//也可以是`*(*pa + i)`
    }
    return 0;
}
  • 数组指针和二维数组
void print1(int arr[3][5], int r, int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
void print2(int(*p)[5], int r, int c)
{
    //传递的只是,第一行的地址。
    //二维数组的数组名表示
	//首元素的地址
	//二维数组的首元素是:第一行
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(((*p) + i)+j));
		}
	}
}
int main()
{
	//打印二维数组
	int arr[3][5] = { { 1,2,3,4,5},{2,4,5,6,3},{4,5,6,7,8} };

	print1(arr, 3, 5);//一般传递数组打印。

	print2(arr, 3, 5);//使用数组指针打印
	
	return 0;
}
不同数组和指针的比较
int main()
{
    int arr[5];//整型数组
    int *arr[5];//整型指针数组 - 存放整型指针的数组
    (int *)arr[5];//同上。
    int(*arr)[10]; //数组指针,该指针能够指向一个数组,数组10个元素,每个元素的类型是int。
    int (*arr[10])[5];/*arr是一个存放数组指针的数组,数组能够存放10个数组指针,每个数组指针能够指向一个数组,数组5个元素,每个元素是int 类型*/
    
    return 0;
}
一维数组传参

一维数组传参,分为 test(int arr[]) | test( * arr) 两种,

一维 数组指针 传参,test(int *arr[ ]) | test(int * *arr)

//一维数组传参
void test(int arr[])
{
	printf("%d", arr[0]);
}
void test(int* ptr)
{
	printf("%d", *ptr);
}
//一维指针数组传参
void test2(int* arr2[])
{
	printf("%d", arr2[0]);
}
void test2(int* *arr)//**两个 星号 **/
{
	printf("%d", *arr);//必须通过*arr才能调用,传递的参数。使用for循环也可以打印数组
}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);

	return 0;
}

关键:从括号包裹来看,int *arr[10]int (*arr)[10] 。操作符的优先级和结合级

  • 二维数组传参
//二维数组传参
void test(int arr[10][10]) 
{  }
void test(int arr[][10])
{  }
void test(int (*arr)[10])/数组指针充当二维数组的参数
{
    //数组指针
    //调用
    *arr[0] = 10;//yes
}
void test(int **arr)
{
    //错误的接受参数。
}
int main()
{
    int arr[10][10]= {0};//初始化
    test(arr);
    return 0;
}

二级指针也是指针,其空间大小和一级指针一样,只是指向的内存层次更深
二级指针,用来保存一级指针的地址
二级指针的大小在32位系统中是4个字节,在64位中是8个字节
指针能比较大小,就是比较地址。
整型指针解引用操作访问4个字节
指针-指针得到指针和指针之间的元素个数


&(&p) - 不行,&p不是一个变量,&pa就行,pa是一个变量。

函数指针

指向含函数的指针,

int Add(int x,int y)
{
    return x + y;
}
int main()
{
    //pf是一个函数指针变量。
    //int (*pf)(int,int) = Add;
    
    //int (*)(int,int)  ->这是数据类型,函数指针类型
    
    int (*pf)(int,int) = &Add;//Add == pf
    int ret = (*pf)(3,5);//1
    int ret = pf(3,5);//2
    int ret = Add(3,5);//3              三者等价。
    
    return 0;
}

数组名 != &数组名 arr != &arr
函数名 == &函数名 Add ==&Add

Add 和pf是等价的,不用加 *,加不加*,都一样。

相关问题
  • ((void()( ))0) ( );

    (*(void(*)( ))0) ( );
    //调用0地址的函数;
    //(void(*)()) - 强制类型转换,为函数指针类型。
    //将地址0,强制转化为函数指针
    // (* (p))( );
    //通过*将其转换为函数名。
    //
    //调用0地址的函数
    //该函数无参,返回类型void
    //返回类型,这一点不知道,不过,硬要说的话,就是void

    void (*signal(int, void(*)(int) ) )(int);
    //分成两块看void(* )(int);and  another
    //signal(),是一个函数,参数两个,一个int(返回类型),一个函数
    //一个void(*)(int) ->(返回类型),
    //该函数指针,指向一个参数为int,返回类型是void的函数。
    //剩下的是强制类型转换。err
    //signed函数的返回类型也是一个函数指针。
    //该函数指针,指向一个参数为int,返回类型是void的函数。
    //signed是一个函数的声明。
    //不是函数调用,定义
    
    //还可以写成
    typedef void (*pfun_t)(int);
    pfun_t signed(int, pfun_t);
    //对void(*)(int)的函数zhi'zhen类型重命名为pfun_t.

函数指针数组
int main()
{
    int (*pf1)(int,int) = &Add;
    int (*pf2)(int,int) = Sub;
    
    int(*pfArr[2])(int,int) = {pf1,Sub};//pfArr就是一个函数指针数组
    int (*pfArr)  (int,int) = Add;//函数指针
    
    return 0;
}
  • 解决一些 代码冗余,使用函数指针数组的方法
int main()
{

	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		int x = 0;
		int y = 0;
		int ret = 0;

		if (input >= 1 && input <= 4)
		{
			printf("请输入两个数字:>\n");
			scanf("%d %d", &x, &y);
			//也叫转移表。
			int(*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };//函数指针数组。
			ret = (pfArr[input])(x, y);
			printf("ret = %d\n", ret);
		}
		else if (input == 0)
		{
			printf("结束循环\n");
			break;
		}
		else
		{
			printf("输入错误请重新输入\n");
		}
	} while (input);
    
	return 0;
}
指向函数指针数组的指针

函数指针的数组 - 数组

取出函数指针数组的地址

    //整型数组
    int arr[5];
    int* parr =&arr;
    
    //整型指针的数组
    int * arr[5]={&a,&b};
    int*(*pa)[5] = &arr;
int main()
{
    int(*p)(int,int);//函数指针
    int(*p2[4])(int,int);//函数指针的数组
    int(*(*p3)[4])(int,int) = &p2;//取出的是函数指针数组的地址。
    //p3就是一个指向【函数指针的数组】的指针
    //结合加减乘除代码
    //学习这个主要是为了学习  回调函数
    return 0;
}

int *arr[5];

分为两部分,

int* —— 数组中的元素的数据类型

int* [5] —— arr的数据类型

回调函数
  • 把一个函数的地址,作为参数传递给别的函数调用的函数方法

  • 加减乘除代码

void menu()
{
	printf("*****************************\n");
	printf("****** 1.Add   2.Sub    *****\n");
	printf("****** 3.Mul   3.Div    *****\n");
	printf("******      0.exit      *****\n");
	printf("*****************************\n");
}
void Add(int x, int y)
{
	return x + y;
}
void Sub(int x, int y)
{
	return x - y;
}
void Mul(int x, int y)
{
	return x * y;
}
void Div(int x, int y)
{
	return x / y;
}
//不知道回调函数的参数怎么写?
//就是函数指针的形式。
void Calc(int (*pf)(int,int))
{
	int x = 0;
	int y = 0;
	scanf("%d %d", &x, &y);
	printf("打印的东西\n");
	printf("%d\n", (pf)(x, y));
}
int main()
{
	int input = 0;
	menu();//打印菜单

	do
	{
		scanf("%d", &input);
		//函数指针数组
		int (*pf[5]) (int, int) = { NULL,Add,Sub,Mul, Div};
		switch (input)
		{
		case 1:
			Calc(Add);
			break;
		case 2:
			Calc(Sub);
			break;
		case 3:
			Calc(Mul);
			break;
		case 4:
			Calc(Div);
			break;
		case 0:
			printf("退出函数\n");
			break;
		default:
			printf("请输入正确的数字\n");
			break;
		}
	} while (input);
	return 0;
}
  • void*类型的指针
int main()
{
    int a = 0;
    char ch = 'w';
    void* p = &a;//ok,无具体类型的指针。
    p = &ch;//ok
    //*p 就报错了,不知到+1后,跳过几个字节。,err  
    // 是不知道访问几个字节,
    //p++  也不行,因为不知道跳过几个字节。
    return 0;
}
  • 梳理
int main()
{
	int a = 10;
	int b = 30;
	int* arr[2] = { &a,&b };//指针数组
	int* (*pa)[2] = &arr;//指向指针数组的指针
	int* (*pb)[2] = arr;//指针
	printf("%d\n", *(*pa));//地址
	printf("%d\n", *(*pb));
	printf("%d\n", (*pa));
	printf("%d\n", (*pb));
	printf("%d\n", **(*pa));//10
	printf("%d\n", **(*pb));//10
	printf("%d\n", ***pa);//10
	printf("%d\n", ***pb);//10

	printf("%d\n", ***pa + 1);
	printf("%d\n", ***pb + 1);

	printf("%d\n", *(**pa + 1));
	printf("%d\n", *(**pb + 1));
	printf("%d\n", **(*pa + 1));//30
	printf("%d\n", **(*pb + 1));//30
	return 0;
}

指针和数组梳理

指针数组传参
一级指针,int* p;char* pc; void* pv;一维数组 int arr[5]={0};
二级指针 ,int**pa; char **pc;二维数组 char arr[5][5] = {0};
数组指针,int (*arr)[5]=&arr;指针数组 int *arr[5] = {&a,&b,&c};
函数指针,void (*pf)(int,int) =Add;函数指针数组 void (*pf[5])(int,int) ={NULLAdd,Sub,Mul,Div};
指向函数指针数组的指针,void (*(pf2)[5])(int,int)=&pf;//函数指针数组的地址
int main()
{
    int *p = NULL;//int*类型
    int arr[10] = {0};//数组的形式,每个元素是int类型
    //
    //p = arr;//arr是int类型的,存放到p中可以,但是为什么啊?int的地址放到整型指针里去,
    //换一种思路思考,arr表示数组首元素的地址,是可以理解成 int*类型的,放到p整型指针中去就可以了。
    
    //int (*ptr)[10] = &arr;//ptr - 数组指针,存放整个数组的地址没问题
    //数组的地址放到,数组指针中去。
    //p = &arr[0];
    //p = &arr;//err
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值