C语言指针类型详解(建议收藏)

目录

一、指针初阶

1.指针是什么?

1.1指针的定义

1.2指针变量的定义

2.指针和指针类型

2.1指针类型不同的意义

2.2指针的解引用

3.野指针

3.1野指针成因

3.1.1指针未初始化

3.1.2指针越界访问

3.1.3指针指向的空间释放

3.2如何规避野指针

4.指针的运算

4.1指针+-整数

4.2指针-指针

4.3指针的关系运算

5.指针和数组

6.二级指针

7.指针数组

二、指针进阶

1.字符指针

1.1一般使用

1.2字符串使用

2.数组指针

2.1数组指针的定义

2.2数组名与&数组名

2.3数组指针的使用

3.数组参数、指针参数

3.1一维数组传参

3.2二维数组传参

3.3一级指针传参

3.4二级指针传参

4.函数指针

5.回调函数


一、指针初阶

1.指针是什么?

1.1指针的定义

指针是内存中一个最小单元的编号,也就是地址(以字节为单位,每一个字节的存储单位具有一个地址)。

1.2指针变量的定义

指针变量是用来存放内存地址的变量;可以通过取地址符&取出某一变量(字符型、整型、数组类型等)的起始地址存放在指针变量中。经常将指针变量称为指针

注意区分:指针变量的大小即地址的大小取决于计算机是32位还是64位;对于32位的计算机,有32根地址线,在寻址时产生32个0或1;即0000 0000 0000 0000 0000 0000 0000 0000~1111 1111 1111 1111 1111 1111 1111 1111,此时有2的32次方个地址(=4GB空闲编址),该指针变量占32/8=4个字节。对于64位的计算机,有64根地址线,在寻址时产生64个0或1;即0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000~1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 ,此时有2的64次方个地址(=16GB空闲编址),该指针变量占64/8=8个字节。指针变量存放着其他变量的地址,而存放着其他变量地址的指针变量也作为其中一种类型的变量存放在内存中。

2.指针和指针类型

char  *pc = NULL;
int  *pi = NULL;
short *ps = NULL;
long  *pl = NULL;
float *pf = NULL;
double *pd = NULL;
int num = 10;
int *pj = num;

指针定义的方式是  type * 指针变量名 ;其中类型是表明该指针变量中存放的是哪种类型变量的地址(如int * pj存放的是int类型的num);如果要将变量存入标有与变量类型不同的指针变量时,要进行强制类型转化:

int n = 10;
char *pc = (char*)&n;
int *pi = &n;

 易得,&n的类型是 int *,要将&n存入char *类型的指针变量中就要先将其强制类型转化为char *型

2.1指针类型不同的意义

printf("%p\n", &n);//000000065911F984
printf("%p\n", pc);//000000065911F984
printf("%p\n", pc+1);//000000065911F985
printf("%p\n", pi);//000000065911F984
printf("%p\n", pi+1);//000000065911F984

 不同类型的指针在+-1或其它整数时跳过的字节数与指针变量定义的类型有关。在n的地址经过强制类型转化后存入char * pc,而指针变量pc与指针变量pi中存的都是n的起始第一个字节地址;不同之处在于当指针pc+1时跳过了1个字节,指针pi+1时跳过了4个字节;原因是char * pc的类型char长度为1个字节,int * pi的类型int为四个字节。

2.2指针的解引用

对指针变量进行解引用(*)操作的时候,指针的类型决定解引用的权限;比如:char *的指针解引用只能访问第一个字节,而int *类型的指针可以访问四个字节。

int n = 256;
char* pc = (char*)&n;
int* pi = &n;
printf("%d\n", *pi);//打印结果:256
printf("%d", *pc);  //打印结果:0

 上述打印结果的原因解释;256以二进制的形式表示为:

0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000

在解引用指针变量pi类型为int *类型时可以访问四个字节(后32bit位):0000 0000 0000 0000 0000 0001 0000 0000(256);在解引用指针变量pc类型为char *类型时只能访问1个字节(后8bit位):0000 0000(0);

3.野指针

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

3.1野指针成因

3.1.1指针未初始化
int *p;//局部变量指针未初始化,默认为随机值,程序运行过程中报错
  *p = 20;
return 0;
3.1.2指针越界访问
int arr[10] = {0};
  int *p = arr;
  int i = 0;
  for(i=0; i<=11; i++)
 {
    //当指针指向的范围超出数组arr的范围时,p就是野指针
    *(p++) = i;
 }
3.1.3指针指向的空间释放

3.2如何规避野指针

int* p = NULL;
int* q = NULL;
int n = 10;
p = &n;
printf("%d", *p);//10
*q = 10;//报错!!!

创建指针变量时初始化,无指向值时使其指向为空,之后该指针变量可以重新指向其他变量,但是由于指针变量置空,在没指向某一变量时不能进行赋值,编译器会报错,因此在对指针变量进行直接赋值操作时,可以先检查它是否为空。

4.指针的运算

4.1指针+-整数

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

4.2指针-指针

int my_strlen(char *s)//计算字符串长度
{
   char *p = s;
   while(*p != '\0' )
       p++;
   return p-s;
}

两个指针相减实际上是两个指针值(地址)相减之差再除以该指针所指变量的的大小(字节数)。上图代码就是通过将p自增至指向s字符串的末位,再减去s原指向的首位得到中间元素的个数。

4.3指针的关系运算

for(vp = &values[MAX]; vp > &values[0];)//代码1
{
  *--vp = 0;
}
for(vp = &values[MAX-1]; vp >= &values[0];vp--)//代码二
{
  *vp = 0;
}//循环在最后一次判断时,vp递减到数组首位元素在内存中的前一位

代码一不可行,代码二可行;标准规定:

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

5.指针和数组

int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
printf("%p\n", arr);//000000C1FA4FF6C8
printf("%p\n", &arr[0]);//000000C1FA4FF6C8

 可见,数组名表示的是数组首元素的地址。但sizeof(arr)!=sizeof(arr[0]);此时,arr表示整个数组,arr[0]表示数组首元素;那么将数组的地址给到指针变量时就可以表示为:

int arr=[1,2,3,4,5];
int* p = arr;
int* q = &arr[0];

指针p、q均解引用后表示 数组的首元素,在表示arr[i]时又可以有多种表达:

arr[i]; *(p+i);* (arr+i)

 p+i指得是数组第i个元素的地址。2.1有提到,再对指针变量进行+-操作时,是以其所指向元素大小为单位;+i即跳过i个该元素大小的字节后所指向的字节它的地址。

6.二级指针

指针变量中存放着地址,那么指针变量也可以存放其他指针变量的地址,该指针变量称为二级指针变量(二级指针)。表示为 type** 变量名;type *表示其所指向的变量类型。

int a = 10;
int *pa = &a;
int **ppa = &pa;
printf("%p\n", ppa);//pa的地址000000EA244FF598
printf("%p\n", *ppa);//pa中存放的a的地址000000EA244FF574
printf("%d\n", **ppa);//先对pa的地址解引用得到a的地址 在对a的地址解引用得到a的值
	

7.指针数组

指针数组表示的是数组中存放的每个元素的类型是指针。即有整型数组、字符数组、指针数组。

int arr[5]; char arr[5]; int* arr3[5];//表示每个元素的类型是 * int

二、指针进阶

1.字符指针

在指针的类型中我们知道有一种指针类型为字符指针char *

1.1一般使用

 char ch = 'w';
 char *pc = &ch;
 *pc = 'w';

1.2字符串使用

const char* pstr = "hello world.";
printf("%s\n", pstr);//hello world.
printf("%c\n", *pstr);//h

const char* pstr = "hello world.";该行代码并不是将hello world.字符串放入了指针变量pstr中,而是将该字符串的首字节(元素)地址放入 了pstr中。那么通过指针变量创建的字符串又和字符数组初始化创建的字符串有什么区别呢?看下面的例子:

char str1[] = "hello world.";
char str2[] = "hello world.";
const char* strp1 = "hello world.";
const char* strp2 = "hello world.";
if (str1 == str2)
	printf("str1 and str2 are same\n");
else
	printf("str1 and str2 are not same\n"); //打印输出

if (strp1 == strp2)
	printf("strp1 and strp2 are same\n"); //打印输出
else
	printf("strp1 and strp2 are not same\n");

字符串的比较要用strcmp();但在此代码中我们直接用来比较是为了判断它们所创建的"hello world."在内存中是不是指的同一串;由打印输出的结果来看,在创建数组str1[ ]和str2[ ]时在内存中创建了两个"hello world.";而在创建指针变量strp1时,"hello world."被创建放置在内存中,再由strp1指向其,又在创建strp2时让其直接指向已经被创建的"hello world.";

2.数组指针

2.1数组指针的定义

指针数组时存放指针的数组,数组指针是指向数组的指针;

int *p1[10];//指针数组
int (*p2)[10];//数组指针

 [ ] 的优先级高于 *。当没有括号时int* p1[10],p1先和[ ]结合表示p1[ ]数组,int *此时表示数组类型,表示存放指针类型变量;当有括号时int (*p2) [10],p2先和*结合,说明p2是一个指针变量,int [10]此时为指针的类型,表示指向数组。

2.2数组名与&数组名

&arr与arr的含义相同吗?

int arr[10] = { 0 };
printf("%p\n", &arr[0]);//000000046BF9FA18
printf("%p\n", arr);//000000046BF9FA18
printf("%p\n", &arr);//000000046BF9FA18
printf("arr+1 = %p\n", arr+1);//000000046BF9FA1C
printf("&arr+1= %p\n", &arr+1);//000000046BF9FA40

在此段代码中arr,&arr打印输出都是数组首元素的地址 ;但打印arr+1跳过了4个字节,&arr+1跳过了40个字节;因为取地址arr取的是arr数组的地址,若存放进指针变量则变量类型为int(*)[10]的数组指针,再进行取地址+1操作时,跳过的字节大小是整个数组。

2.3数组指针的使用

数组指针可以用来在二维数组传参时做形参。

void print_arr2(int (*arr)[5], int row, int col)//相当于int arr[3][5]
{
  int i = 0;
  for(i=0; i<row; i++)
 {
    for(j=0; j<col; j++)
   {
      printf("%d ", arr[i][j]);
   }
    printf("\n");
 }
}

3.数组参数、指针参数

3.1一维数组传参

void test(int arr[]){}//正确
void test(int arr[10]){}//正确
void test(int* arr){}//正确
void test2(int* arr[20]){}//正确
void test2(int** arr){}//正确
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

3.2二维数组传参

void test(int arr[3][5]){}//正确
void test(int arr[][]){}//错误
void test(int arr[][5]){}//正确
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
void test(int* arr){}//正确
void test(int* arr[5]){}//正确
void test(int(*arr)[5]){}//正确
void test(int** arr){}//正确
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

3.3一级指针传参

void test1(int *p)
{}//test1函数能接收int类型变量的地址
void test2(char* p)
{}//test2函数能接收char类型变量的地址

3.4二级指针传参

void test(char **p){}//能接收存放字符型变量地址的指针变量的地址
int main()
{
char c = 'b';
char*pc = &c;
char**ppc = &pc;
char* arr[10];
test(&pc);
test(ppc);
test(arr);//指针数组(arr表示数组首元素的地址,数组首元素是存放char类型变量的指针)
return 0;
}

4.函数指针

void test(){printf("hehe\n");}
int main()
{
printf("%p\n", test);//00007FF618AB1406
printf("%p\n", &test);//00007FF618AB1406
}//函数也具有地址

 函数具有地址并且它的地址可以用指针保存,该指针类型为函数指针。格式为:

void (*pfunction) (int ,char );

 函数返回值类型(*函数指针变量名)(函数参数类型【没有参数时可为空】)

//代码一:
void (*signal(int , void(*)(int)))(int);
//代码二:
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

代码一和代码二相同。这是一个函数的声明:signal函数有两个参数,其中一个是整型,另一个参数是返回值为空、有一个整型参数的函数指针类型;而signal函数的返回值是void (*)(int)类型,即signal函数的返回值是,一个无返回值有一个整型参数的函数指针类型。

5.回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。

典型回调函数:qsort函数的使用

//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)//void表示指针是无指向类型
{
 return (*( int *)p1 - *(int *) p2);//需要先将无类型的指针强制转换为要比较的类型
}
int main()
{
  int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
  int i = 0;
  qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
  for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
 {
   printf( "%d ", arr[i]);
 }
  printf("\n");
  return 0;
}

qsort函数C语言编译器函数库自带的排序函数。qsort 的函数原型是

void qsort(void*base,size_t num,size_t width,int(__cdecl*compare)(const void*,const void*));         在传参时,需要传入数组的地址,数组的元素个数,数组单个元素的长度,以及排序判断函数。qsort最后一个参数就是函数指针类型,在使用qsort函数进行排序时,就要调用比较函数。

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: "C语言指针详解.pdf" 是一份详细介绍C语言指针概念和使用的PDF文档。C语言指针是一种特殊的变量类型,用于存储其他变量的内存地址。 该PDF文档首先详细介绍了指针的定义和声明。指针的声明需要指定指针变量类型和名称,并使用星号(*)来表示该变量是一个指针指针变量名的前面加上一个星号,可以获取所指向的变量的值,这被称为"解引用"。 文档还介绍了指针的运算。指针可以进行自增和自减运算,指针之间可以进行相减操作,返回的结果表示它们之间的距离或者偏移量。此外,还可以将指针赋值给另一个指针,或者将指针赋值给一个变量,反之亦然。 除了基本的指针概念,文档还详细介绍了指针的常见应用场景。这包括指针作为函数参数,用于在函数内部对传入的变量进行修改。还有通过指针来实现动态内存分配和释放,以及使用指针实现数据结构(如链表和树)等。 此外,该文档还包含一些常见的指针错误和问题的解决方案。这些错误包括空指针引用、野指针引用以及内存泄漏等。文档指出了这些错误的影响以及如何避免它们。 总的来说,"C语言指针详解.pdf" 是一份详细介绍C语言指针概念、使用和常见问题解决方案的文档,对于学习和理解C语言指针的人们是一份宝贵的资料。 ### 回答2: 《C语言指针详解.pdf》是一本关于C语言指针的详细解析的电子书。在这本书,作者详细介绍了C语言指针的概念、用途和基本语法。 首先,指针C语言非常重要的概念,它是一种数据类型,用于存储和操作内存地址。指针可以指向各种数据类型,如整数、字符、数组和结构体等。 在《C语言指针详解.pdf》,作者详细讲解了指针的声明和初始化,以及如何通过指针来访问和修改变量的值。作者还介绍了指针与数组的关系,以及指针和函数之间的关联。 此外,书还涵盖了指针的高级应用,如指针的算术运算、指向指针指针指针数组等。作者通过丰富的例子和代码来帮助读者理解这些概念和技巧。 《C语言指针详解.pdf》不仅适合C语言初学者,也适合有一定编程基础的读者。通过阅读此书,读者将能够更深入地理解C语言指针的功能和用法,掌握指针在编程的灵活运用。 总之,《C语言指针详解.pdf》是一本内容详尽且易于理解的C语言指针教程。读者通过阅读此书,可以提高自己在C语言编程指针应用能力,从而更好地实现程序的设计和开发。 ### 回答3: 《C语言指针详解.pdf》是一本介绍C语言指针概念和使用方法的详细手册。C语言指针是一种非常重要和特殊的数据类型,它提供了直接访问内存地址的能力,使得C语言具有了更高的灵活性和效率。 这本手册首先会介绍指针的基本概念,包括指针变量的定义和声明、指针的初始化和赋值。它会详细讲解指针变量之间的关系,以及指针的运算规则和使用方法。读者可以学习到如何通过指针操作变量的值和地址,以及如何利用指针实现函数的参数传递和返回值。 接下来,手册会介绍指针和数组之间的关系。C语言,数组名本质上是一个指向数组首元素的常量指针,因此可以通过指针来操作数组。手册将详细讲解指针和数组的指针算术运算,以及指针和多维数组的关系。 此外,手册还会介绍指针和字符串之间的关系。C语言,字符串本质上是以空字符结尾的字符数组,可以通过指针来操作字符串。手册将详细讲解指针和字符串的操作,包括字符串的输入输出、字符串的比较和拷贝。 最后,手册还会介绍指针和结构体之间的关系。C语言,结构体是用户自定义的复合数据类型,可以通过指针来操作结构体。手册将详细讲解指针和结构体的操作,包括结构体指针的定义和使用,以及结构体指针作为函数参数的传递方式。 总之,《C语言指针详解.pdf》是一本深入浅出的指针教程,对于想更深入理解C语言指针的读者来说,是一本非常实用的参考书。无论是初学者还是有一定基础的读者,都可以从获得很多宝贵的知识和技巧。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值