指针基础

指针的主题,我们在初级阶段的《指针》章节已经接触过了,我们知道了指针的概念:

1.指针就是变量,是用来存地址的,地址是唯一标识一块内存空间
2.指针的大小是固定的4/8个字节(由平台决定,32位/64位)
3.指针是有类型的,通常类型有char、int 、short、long、float、double
4.指针类型决定了指针±整数的步长,指针解引用操作的时候的能够访问空间的大小
5.指针的运算。

  • 指针±整数(==>指针指向的是该地址的上一个或下一个地址)
  • 指针-指针(指向同一内存空间,可得指针之间的元素个数)
  • 指针的关系运算(比较地址大小)

字符指针

指针类型中存在一种字符指针char*

一般使用:

int main()
{
char ch= 'w';
char *pc = &ch;
*pc ='w';
return 0;
}

另一种使用方式:

int main()
{
   const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
   printf("%s\n", pstr);
   return 0;
}

注:

(一)常量指针int const* p 和 const int* p

const 放在指针变量的 * 左边时,表示该指针所指向的数据(地址内的内容)是常量,即不能通过该指针修改所指向的数据。但p所指向的地址可以变。

>int num = 10;
>const int *p = # // 将const放在*的左边,表示所指向的数据是常量
>*p = 20; // 编译错误,因为p所指向的数据是常量
>p++; // 合法,p本身不是常量

在上面的例子中,p 是一个指向 num的常量指针,即不能通过 p修改 num的值。但是可以通过 p 修改其指向的地址。

注**:int const* p定义了一个指向 const int类型数据的指针变量 p,也就是说,p 所指向的数据是一个常量,不能通过 p修改其所指向的数据**。但是 p本身不是常量,可以指向其他的 const int类型数据。

(二)指针常量int* const p

const放在指针变量的*右边时,表示指针本身是常量。它指向的地址是不可改变的,但地址里的内容可以通过指针改变。

>int num = 10;
>int *const p = # // 将const放在*的左边,并且指针本身也是常量
>*p = 20; // 合法,可以通过p来修改所指向的数据
>p++; // 编译错误,因为p本身是常量,不能修改其指向的地址

在上面的例子中,int* const p就是一个指针常量,表示定义了一个指向 int 类型的指针变量 p,且这个指针变量是一个常量,其值(地址)不能被改变,但它所指向的 int类型数据(地址内的内容)可以被修改。

总结:

一般是根据靠近原则来看,const修饰p那么就是指针本身值(即指向的地址)不变,const修饰*p那么就是指指针指向的变量值(指向的值)不变

加深记忆记住三句话:

  • 指针和 const 谁在前先读谁 ;

  • *象征着地址,const象征着内容;

  • 谁在前面谁就不允许改变。

int main()
{
char arr1[] = "abcdefk";
char arr2[] = "abcdefk";
const char* p1 = "abcdefk";//const使变量
const char* p2 = "abcdefk";//常量字符串的地址会在内存中单独开辟一个空间,地址固定,
if (p1 == p2)
{
printf("hehe\n");
}
else
{
printf("haha\n ")
}
//if (arr1 = arr2)
//{
// printf("hehe\n");
//}
//else
//{
// printf("haha\n");
//}
return 0;
}

这里p1和p2指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化
不同的数组的时候就会开辟出不同的内存块。所以arr11和arr2不同,p1和p2不同。 来源《剑指offer》

指针数组

预备知识:

  • 1.&arr-数组名-此时数组名不是首元素的地址–数组名表示整个数组–&数组名 取出的是整个数组的地址。

  • 2.sizeof(arr)-sizeof(数组名)-数组名表示整个数组-sizeof(数组名)计算的是整个数组的大小。

  • 除1、2以外,数组名表示数组的首元素的地址

>int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组```

```c
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* parr[] = { arr1,arr2,arr3 };
return 0;
}

image-20230422002000521

指针数组用法:

int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };

int* parr[] = { arr1,arr2,arr3 };
int i = 0;
for (i = 0;i < 3;i++)
{
int j = 0;
for (j = 0;j < 5;j++)
{
printf("%d ", *(parr[i] + j));
}
printf("\n");
}
return 0;
}

数组指针

数组指针的定义

数组指针是指针

整形指针:int * pint; 能够指向整形数据的指针。浮点型指针:float * pf; 能够指向浮点型数据的指针。数组指针:能够指向数组的指针

int *p1[10];//==int* p1[10] 指针数组
int (*p2)[10];//数组指针
//p1, p2分别是什么?

指针数组,对于语句“int* p1[10]”,因为“[]”的优先级要比“*”要高,所以 p1 先与“[]”结合,构成一个数组的定义,数组名为 p1,而“int*”修饰的是数组的内容,即数组的每个元素。也就是说,该数组包含 10个指向 int 类型数据的指针,如图 1 所示,因此,它是一个指针数组

Attachment

​ 图 1

数组指针,对于语句“int(*p2)[5]”,“()”的优先级比“[]”高,“*”号和 p2 构成一个指针的定义指针变量名为 p2,**而 int 修饰的是数组的内容,即数组的每个元素。**也就是说,p2 是一个指针,它指向一个包含 10 个 int 类型数据的数组,如图 2 所示。很显然,它是一个数组指针,数组在这里并没有名字,是个匿名数组

Attachment

​ 图 2

由此可见,

对指针数组来说,首先它是一个数组,数组的元素都是指针,也就是说该数组存储的是指针,数组占多少个字节由数组本身决定;而对数组指针来说,首先它是一个指针,**它指向一个数组,**也就是说它是指向数组的指针,>在 32 位系统下永远占 4 字节,至于它指向的数组占多少字节,这个不能够确定,要看具体情况。

int main()
{
//int* p = NULL;//p是整形指针 - 指向整形的指针 - 可以存放整形的地址
//char* pc = NULL;//pa是字符指针 - 指向字符的指针 - 可以存放字符的地址
//数组指针 - 指向数组的指针 -可以存放数组的地址
//int arr[10] = { 0 };
//arr-首元素地址
//&arr[0]-首元素的地址
//&arr-数组的地址(整个数组)

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int (*p)[10] = &arr;//数组的地址要存起来
//上面的p就是数组指针
return 0;
}

例:

int main()
{
char* arr[5];//指针数组
char* (*pa)[5]=&arr;
//*pa指针指向数组5个元素,即(*pa)[5];而指向的数组元素类型是char*。
//故取数组的地址&arr写成:char* (*pa)[5]=&arr;

return 0;
}
image-20230422185558143

&数组名VS数组名

&arr是取的整个数组的地址

数组指针用法

用法1:

int(*pa)[10] = &arr;
int i = 0;
for (i = 0;i < 10;i++)
{
printf("%d ", *(*pa+i));//*pa==arr
}

用法2:地址传参

二维数组看作一维数组,则其数组名就是首元素的地址

arr[i] == *(a+i) == *(p+i) ==  p[i]

*(*(p + i) + j)== *(p[i] + j)==(*(p + i))[j]==p[i][j]

例:

(*(p + i) + j);//(p + i)找到n维数组第i行的地址,(*(p + i) + j)找到n维数组第i行的第j列元素的地址
// 参数是数组的形式
//void print1(int arr[3][5], int x, int y)
//{
// //int i = 0, j = 0;
// //for (i = 0;i < x;i++)
// //{
// // for (j = 0;j < y;j++)
// // {
// // printf("%d ", arr[i][j]);
// // }
// // printf("\n");
// //}
//
// return 0;
//}

//参数是指针的形式
void print2(int (*p)[5], int x, int y)//int (*p)[5] - 数组指针p是指向含5个元素为int类型的数组
{
int i = 0;
for (i = 0;i < x;i++)
{
int j = 0;
for (j = 0;j < y;j++)
{
printf("%d ", p[i][j]);
// printf("%d ", *(p[i] + j));
//printf("%d ", * (*(p + i) + j));//(p + i)找到n维数组第i行的地址,(*(p + i) + j)找到n维数组第i行的第j列元素的地址
//printf("%d ", (*(p + 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} };
//print1(arr, 3, 5);//arr - 数组名 -数组名就是首元素的地址
print2(arr, 3, 5);
   //数组名arr,表示首元素的地址
   //但是二维数组的首元素是二维数组的第一行
   //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
   //可以数组指针来接收

//int arr[10]={1,2,3,4,5,6,7,8,9,10}
//int i = 0;
//int* p = arr;
//for (i = 0;i < 10;i++)
//{
// //printf("%d ", *(p + i));
// //printf("%d ",p[i]);
// //printf("%d ", *(arr + i));
// printf("%d ", arr[i]);//arr[i] == *(a+i) == *(p+i) == p[i]
//}
return 0;
}

练习:

int arr[5];//arr是一个含5个元素的整形数组
int *parr1[10];//parr1是一个数组,数组有10个元素,每个元素的类型是int*,故parr1是指针数组
int (*parr2)[10];//parr2是数组指针,它指向一个含10个元素的数组,数组的元素类型是int
int (*parr3[10])[5];//parr3是数组,该数组的有10个元素,每个元素是一个数组指针;该数组指针指向的数组有5个元素,每个元素类型是int

数组参数、指针参数

一维数组传参

#include <stdio.h>
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int *arr)//ok
{}
void test2(int *arr[20])//ok
{}
void test2(int **arr)//ok//一级指针的地址存放在二级指针里 int **arr
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}

二维数组传参

二维数组的数组名是首元素的地址,传参时,传入的是数组第一行地址

void test(int arr[3][5])
{}
void test1(int arr[][5])//数组传参,行可以省略,列不可以省略
{}
void test2(int arr[][])//error
{}
void test3(int *arr)//error,二维数组无法存放整形指针中去
{}
void test4(int** arr)//error,数组名是第一行地址,而二级指针存放是一级变量(指针)的地址
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test5(int* arr[5])//ok?//error
{}
void test6(int (*arr)[5])//ok?//ok
{}

int main()
{
int arr[3][5] = {0};
test(arr);
}

一级指针传参

#include <stdio.h>
void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}

思考:

当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

1.void test1(int *p)
{}
//test1函数能接收什么参数? test1(&a);或test1(p1);
int main()
{ int a=10;
int* p1=&a;
test1(&a);
test1(p1);
return 0;
}

2. void test2(char* p)
{}
//test2函数能接收什么参数? test2(&ch);或test2(str);
int main()
{ char ch='w';
char* str=&ch;
test2(&ch);
test2(str);
return 0;
}

二级指针传参

#include <stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int *p = &n;
int **pp = &p;//取一级指针p的地址
test(pp);
test(&p);

int* arr[10];//指针数组,数组里每个元素都是一级指针
test(arr);//传过去的是arr数组首元素的地址
return 0;
}

思考:

当函数的参数为二级指针的时候,可以接收什么参数?

>void test(char **p)
>{

>}
>int main()
>{
char c = 'b';
char*pc = &c;
char**ppc = &pc;
char* arr[10];//指针数组,数组里每个元素都是一级指针
test(&pc);
test(ppc);
test(arr);//Ok?//ok//传过去的是arr数组首元素的地址
return 0;
>}

函数指针

&函数名 和 函数名 都是函数的地址

函数指针定义示例:

int Add(int x, int y)
{}
int (*pa)(int ,int ) = Add;
printf("%d\n", (*pa)(2, 3));
//函数指针 - 是指向函数的指针 -存放函数地址的指针
int Add(int x, int y)
{
int z = 0;
return z = x + y;
}
int main()
{
int a = 10;
int b = 20;
/*printf("%d ", Add(a, b));*/
//&函数名 和 函数名 都是函数的地址
//printf("%p\n", &Add);//打印出函数的地址
//printf("%p\n", Add);//打印出函数的地址

int (*pa)(int ,int ) = Add;
printf("%d\n", (*pa)(2, 3));//5
return 0;
}

用法:

void Print(char* str)
{
printf("%s\n", str);
}

int main()
{
void (*p)(char*) = Print;
(*p)("hello bit");
return 0;
}

课件:

void test()
{
printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
//pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
void *pfun2();
//pfun2表明这是一个函数,其返回类型是void*