指针(2)

指针

    • 野指针
      • 野指针的成因
      • 野指针防范措施
        • assert
    • 字符指针变量
    • 二级指针变量
        • 指针数组
        • 指针数组模拟二维数组
    • 数组指针变量
        • 定义
        • 二维数组的传参本质
          • 二维数组的普通传参
          • 形参为指针的二维数组的传参
    • 函数指针变量
      • 定义
      • 函数指针类型
      • 函数指针数组

本文介绍了野指针及其防范措施字符指针变量二级指针及指针数组数组指针变量及其传参本质函数指针变量函数指针数组

指针(2)_第1张图片

野指针

定义:野指针就是指向的位置是不可知的(随机的,不正确的,没有明确限制的)

野指针的成因

  • 指针未初始化
#int main()
int main()
{
	int *p;//局部变量指针未初始化,默认为随机值
	*p=10;
	return 0}
  • 指针越界访问
#include
int main()
{
	int arr[10]={ 0 };
	int* p = arr;
	for(int i=0;i<11;i++)
	{
		//当i=10的时候,指针p超出数组arr的范围,p就成了野指针
		*(p++) = i;
	}
	return 0;
}
  • 指针指向的空间释放
#include

int* test()
{
	int a = 10;
	return &a;
}

int main()
{
	int* p=test();//这里返回的空间已经被释放,得到的是野指针
	return 0;
}

野指针防范措施

  • 指针初始化
    如果没有要明确指向的目标,可以给指针赋值为NULL。
    NULL:在C语言中是一个标识符常量,地址为0,数值为0,无法使用也无法被正确读写。
int main()
{
	int a = 10;
	int* pa = &a;
	int* pb = NULL;
	
	return 0;
}
  • 注意指针是否越界访问
    ⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。

  • 在使用完指针后,养成将其置空的习惯

#include
int main()
{
	int a = 1;
	int* pa = &a;
	printf("%d",*pa);
	//使用过程....
	pa = NULL;
	return 0;
}
  • 指针在使用前使用assert进行断言,检查指针是否有效
#include
#include

int main()
{
	int p1 = NULL;
	int a = 10;
	int* p2 = &a;
	
	assert(p2);//p2不为NULL,程序继续运行
	assert(p1);//p1为NULL,assert()进行报错,并在屏幕中打印出该错误信息并标明多少行
	printf("%p %p",p1,p2);
	return 0;
}
assert
#include
void assert(int expression);

assert() 宏接受⼀个表达式作为参数,若这个参数为真(结果为true),assert()不会产生任何作用
程序继续运行;反之,这个参数为假(结果为false),assert()就会报错,在标准错误流stderr中写入一条错误信息,显示没有通过的表达式,包括这个表达式的文件名与行号。

#define NDEBUG
若我们已确保程序没有问题,便可以在#include语句的前面,定义一个宏NDEBUG,这样便可以禁用文件中所有的assert()语句。

#define NDEBUG
#include

若在程序中再次出现问题,我们也可以将#define NDEBUG指令注释掉,再次编译调试

字符指针变量

在指针的类型中,字符指针为char*
一般使用:

#include
int main()
{
	char ch = 'a';
	char* pch = &ch;
	printf("%c",*pch);
	return 0;
}

另一种使用方式:

#include
int main()
{
	char* pstr = "hello,world";
	printf("%s",pstr);
	return 0;
}

上述代码char* pstr=“hello,world”;指的是常量字符串hello,world的首地址放至pstr中,而不是将hello,world放到字符指针pstr中。

《剑指offer》中收录了一道和字符串相关的笔试题,如下:

#include 
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");
 
 if(str3 ==str4)
 printf("str3 and str4 are same\n");
 else
 printf("str3 and str4 are not same\n");
 
 return 0;
}

分析:

  1. str1与str2是先定义一个字符串数组,在将字符串hello,bit放到字符串数组str1,str2,所以这里str1与str2的地址是不同的
  2. str3与str4都是先定义一个字符指针变量,在将常量字符串的首地址赋给str3与str4,所以str3与str4都指向hello,bit的首地址,所以str3==str4成立。

二级指针变量

指针变量也是变量,是变量就有地址,因此也存在二级指针。

#include
int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	return 0;
}
  • *ppa通过对ppa的地址的解引用,这样就可以找到pa,所以*ppa访问的就是pa
		int a= 10;
		*ppa = &a;//pa = &a;
  • *ppa先通过ppa找到pa,然后对pa解引用来访问a
	**ppa = 20;
	//*pa = 20;
	//a= 20;
指针数组

顾名思义,指针数组的每个元素都是用来存放地址的,所以,指针数组的每一个元素都是地址,其中每个地址也都可以指向一块区域,例如:
指针(2)_第2张图片

指针数组模拟二维数组
#include
int main()
{
	int arr1[]={1,2,3,4,5};
	int arr2[]={6,7,8,9,10};
	int arr3[]={11,12,13,14,15};
	int *parr[3]={arr1,arr2,arr3};
	int 1=0 , j=0;
	int col=sizeof(parr)/sizeof(parr[0]);
	int row=sizeof(arr1)/sizeof(arr1[0]);
	for(i=0;i<col;i++)
	{
		for(j=0;j<row;j++)
		{
			printf("%d",parr[i][j]);
		}
		printf("\n");
	}
	
	return 0;
}

parr[i]是parr数组的元素,parr[i]找到的数组元素指向了一位数组的元素,所以parr[i][j]就可以表示在parr[i]中arr(i)[j]的元素

注意:上述代码只是模拟出二维数组的效果,实际上并不是二维数组,因为每一行并非是连续的。

数组指针变量

定义

数组指针变量是存放数组的地址是指向数组的指针变量

int (*p)[10];

p先与*结合,说明p是一个指针变量,然后指着指向一个大小为10的整形数组,所以,p是一个指针,指向一个数组,叫做数组指针。
注意:[]的优先级要高于 * 号,所以,要加上括号来保证*与p最先结合。

二维数组的传参本质
二维数组的普通传参
#include

void Print_arr(int arr[3][5],int x,int y)
{
	for(int i=0;i<x;i++)
	{
		for(int j=0;j<y;j++)
		{
			printf("%d ",arr[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
	Print_arr(arr,3,5);
	return 0;
} 

根据数组名就是这个数组的首元素地址这个规则,如果我们把3个一维数组arr1,arr2,arr3看作3个元素,在将其放置到另一个一维数组nums中,那么nums的首元素就是arr1这个一维数组。
根据上面的例子,第一行的数组类型是int [5],所以第一行的地址类型就是数组指针类型int (*)[5]
所以,这就意味着二维数组的传参本质也就是传递了地址,传递的是这一个一维数组的地址,那么我们的形式参数就可以使用指针来接收,如下:

形参为指针的二维数组的传参
#include

void Print_arr(int (*pnums)[5],int x,int y)
{
	for(int i=0;i<x;i++)
	{
		for(int j=0;j<y;j++)
		{
			printf("%d ",pnums[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
	Print_arr(arr,3,5);
	return 0;
} 

函数指针变量

我们先观察下面的一个代码:

#include

void test()
{
    ;
}
int main()
{
    printf(" test = %p\n",test);
    printf("&test = %p\n",&test);
    return 0;
}

运行结果如下:

 test = 00007FF7785C13F7
&test = 00007FF7785C13F7

由此,我们可以得出函数也有地址,而函数名就是函数的地址,当然也可以使用&函数名来表现。
因此,我们也可以使用指针指向函数的地址,也就是创建函数指针变量。

定义

函数指针变量用来存放函数的地址,通过地址调用函数

函数指针类型

#include
#include
void print_1()
{
    printf("test_1 no problem\n");
}

int cmp_max(int x, int y)
{
    if (x < y)
        x = y;
    return x;
}

char* my_strcat(char* dest, char* src)
{
    assert(dest);
    if (src == NULL)
        return dest;
    char* ret = dest;
    while (*dest)
    {
        dest++;
    }
    while (*dest++ = *src++)
    {
        ;
    }
    return ret;
}

int main()
{
    void(*pv)(void) = print_1;
    int(*pi)(int x, int y) = cmp_max;
    char* (*pc)(char* x, char* y) = my_strcat;
    (*pv)();
    printf("cmp_max(4,2)=%d\n", (*pi)(4, 2));
    char dest[50] = "my_strcat ";
    char src[20] = "no problem\n	";
    printf("%s", (*pc)(dest, src));
    return 0;
}

运行结果如下:
指针(2)_第3张图片

函数指针类型解析:
指针(2)_第4张图片

int (*) (int x,int y)//cmp_max函数指针变量
void (*) (void)//print_1函数指针变量
char* (*) (char* a,char* b)//my_strcat函数指针变量

函数指针数组

既然有函数指针变量,那么我们也可以把函数指针当作元素存放到指针数组中,这就叫做函数指针数组。
定义形式:(返回类型) (*(函数名)[存放个数]) (参数类型)
例如:

int (*parr[5]) (int x,int y)
//这里的函数指针数组可以存放五个函数指针,函数指针类型为int (*) (int x,int y)

例如:计算器的简易实现

#define _CRT_SECURE_NO_WARNINGS 1
#include

void menu()
{
	printf("+-------------------+\n");
	printf("|    1.add(x,y)     |\n");
	printf("|    2.sub(x,y)     |\n");
	printf("|    3.mul(x,y)     |\n");
	printf("|    4.div(x,y)     |\n");
	printf("|    0.退出程序     |\n");
	printf("+-------------------+\n");
}

int add(int x, int y)
{
	return x + y;
}

int sub(int x, int y)
{
	return x - y;
}

int mul(int x, int y)
{
	return x * y;
}

int div(int x, int y)
{
	return x / y;
}

int main()
{
	menu();
	int input = 0;
	int (*pi[5])(int x, int y) = { 0,add,sub,mul,div };
	while (printf("输入你的选择:"), scanf("%d", &input), input)
	{
		if (input > 0 && input < 5)
		{
			int a = 0, b = 0;
			printf("请输入你要计算的数:");
			scanf("%d %d", &a, &b);
			printf("%d\n", (*pi[input])(a, b));
			//利用(*pi[input])(a, b)函数指针调用函数,从而返回计算结果
		}
	}
	printf("成功退出\n");
	return 0;
}

这里通过改变input,来调用函数指针,进而使用函数来进行计算。

好了,以上就是本期的全部内容,喜欢请多多关注吧!!!

你可能感兴趣的:(指针,c语言)