C语言-指针

发布于:2025-05-28 ⋅ 阅读:(39) ⋅ 点赞:(0)

一、指针是什么

指针是内存单元的最小编号,也就是地址;通常口语中的指针指的是指针变量

二、指针和指针类型

int* ,char* ,short* ,double*,float*,结构体指针

无论是什么类型的指针变量,他在内存中的大小都是一样。如:char类型占1字节,int类型占4字节;但是char*和int*在64位操作系统中,都占8字节;

也就是说,无论是何种类型的指针,其大小是依据操作系统的环境决定。

那么所有的指针类型大小都一样,为何还要区分类型呢?

虽然所有的指针类型大小都一样,但是不同类型的指针,在解引用时,其可访问的字节数量是不一样的。如:char*类型指针其大小为4字节(32位系统),通过*解引用时,由于char只有1字节,所以只能修改一字节的数据;而int*,由于int占4字节,那么通过*解引用时,就可以修改4字节的数据。

其次,不同类型的指针,在对指针进行+-操作时,影响其步长

int* 的指针+1,其结果地址增加了4个字节

char* 的指针+1,其结果地址增加了1个字节

三、野指针

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

野指针问题:

1、指针未初始化

2、指针访问越界

3、指针指向的空间释放

如何避免野指针:

1、指针初始化

2、小心指针越界

3、指针指向空间及时释放

4、避免返回局部变量地址

5、指针使用前检查有效性

四、指针运算

1、指针+-整数

指针+-整数 =指针当前地址 +- (指针的类型所占的字节个数 * 整数)

比如:int* p;p++ 相当远将p的地址+上4字节,如原来的地址0x01,p++后等于0x05;

2、指针 - 指针

指针 - 指针 = 指针与指针之间元素的个数

int arr[10] = { 0 };
int ret = &arr[9] - &arr[0];  //ret = 9

 指针 - 指针的前提:这两个指针必须指向同一块空间才可以相减。

3、指针的关系运算

指针与指针之间可以比较大小

C语言规定,指针在比较时,允许与数组中最后一个元素后面一个的内存地址进行比较,不允许与数组的第一个元素的前一个地址进行比较。

允许p1与p2比,不允许p1与p3比较

五、指针和数组

数组:一组相同类型元素的集合

指针:地址

六、二级指针

二级指针:存放指针地址的指针

int main() {
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;   //二级指针

	printf("%p\n",pa);
	printf("%p ---- %d",*ppa,**ppa);
	return 0;
}
int * pa = &a; // * 表示pa是指针变量,int 表示pa指向的是一个整形变量
int* * ppa = &pa  //*表示ppa是指针变量,int*表示ppa指向的是整形指针

二级指针是用来存放一级指针变量的地址;

七、指针数组

存放指针的数组

int* parr[10] = {NULL};

八、数组指针

指向数组的指针:

int arr[10];
int (*p)[10] = &arr;
/*
*(*p) :表示是指针
*(*p)[]:表示这个指针指向数组
*(*p)[10]:表示这个指针指向的数组大小为10
*int (*p)[10]:表示定义了一个指向大小为10的整形数组
*int (*p)[10] = &arr;表示将arr数组的地址赋给这个数组指针。
*&arr:表示整个数组的地址
*p+1:&arr + 0x28,地址偏移40个字节
*/

数组指针的类型:int (*p)[10] = &arr,p的类型int (*)[10],去掉p就是他的类型;

int (*p)[10] = &arr;//表示数组指针,是一个指针
int* p[10] = {0};//表示指针数组,是一个数组

整形指针:指向整型的指针 int*,字符指针:指向字符型的指针:char*

int* arr[10]:arr[] 数组,int* 整形指针,所以int* arr[10]是指针数组,存放指针类的数组

int (*p)[10]:*p:指针,[10]:数组,*p[10] 指针指向[10]数组,int:整形,所以int(*p)[10]:指向int型数组[10]的指针。所以p是一个指向int [10]的数组指针。

1、数组名

数组名通常表示数组的首元素的地址;

例外:

sizeof(数组名):这里的数组名表示整个数组,计算的是整个数组的大小,单位字节

&数组名:表示的是整个数组,表示得到整个数组的地址

九、数组参数和指针参数

1、数组与指针

int arr[5];  //arr整形数组
int *parr[5]; //指针数组
int (*parr)[5]; //数组指针,指针类型:int (*)[5];
int (*parr[10])[5]; //parr[10]:存放int(*)[5] 类型的数组指针的数组,大小为10;

---------------------------------------------------------------------------------------------------------------------------------

2、一维数组传参

void test(int arr[]){};  //test(arr);实参传递一个数组,形参使用一个数组接受,形参数组可以没有大小
void test(int arr[10]){};//形参实参都是数组,且数组大小一致,可以
void test(int *arr){};//test(arr);arr表示首元素地址,使用int * 类型的指针可以接受
void test2(int *arr[20]){};//实参传递类型:指针数组;形参接受类型:指针数组,大小与实参一致,可以
void test2(int **arr){};//arr2:表示数组首元素的地址,arr2存放的是int*类型地址,int **表示首元素,可以 

int main()
{
    int arr[10] = {0};
    //arr2 是一个指针数组,数组存放int*的指针
    int* arr2[20] = {0};
    test(arr);
    test2(arr2);
}

3、二维数组传参

void test(int arr[3][5]){};  //形参3行5列整形数组,实参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);
}

4、一级指针传参

void test(int* p){};//一级指针传参,实参必须是地址,却类型需要与形参类型一致

5、二级指针传参

void test(int** p){};//二级指针形参,实参需要也是二级指针

6、函数指针

指向函数的指针

void func(int x,int y)
{
    printf("%d\n",x+y);
}
int main()
{
    //数组指针,指向数组的指针
    int arr[5] = {0};
    int (*parr)[5] = &arr;
    //函数指针,指向函数的指针
    void (*pf)(int x,int y) = &func; //void (*pf)(int x,int y) = func; 
    printf("%p\n",fp);
    
    //通过函数指针,调用函数
    int ret = pf(2,3);  //int ret = (*pf)(2,3);
}

函数指针的用途:回调函数

7、函数指针数组

存放函数地址的数组

int add(int x,int y)
{
    int ret = x + y;
    return ret;
}
int sub(int x,int y)
{
    int ret = x - y;
    return ret;
}


int main()
{
   //定义
    int (*arr[2])(int,int) = {add,sub};
    //调用
    int res = arr[0](2,4);
}

8、指向函数指针数组的指针

int add(int x,int y)
{
    int ret = x + y;
    return ret;
}
int sub(int x,int y)
{
    int ret = x - y;
    return ret;
}


int main()
{
   //定义函数指针数组
    int (*funcArr[2])(int,int) = {add,sub};
    //定义指向函数指针数组的指针
    int (*(*pFuncArr)[2])(int,int) = &funcArr;
}

9、回调函数

回调函数:通过函数指针调用的函数,当该指针调用了其所指向的函数时,就成为回调函数。

1、void* 指针



//库函数qsort()
void qsort(void* base, //排序的起始位置
            size_t num, //排序元素的个数
            size_t width, //排序元素的大小(单位字节)
            int(* cmp)(const void* el1,const void* el2) //函数指针-比较函数
);

void* x;
//void* 类型的变量,不能直接通过*x解引用,void* 指针是没有具体类型的指针,void* 可以接受任意类型的指针,所以不能解引用操作,也不能进行指针运算操作
//对void* x解引用
*(int*)x = 0;

void* 类型的变量,不能直接通过*x解引用,void* 指针是没有具体类型的指针,void* 可以接受任意类型的指针,所以不能解引用操作,也不能进行指针运算操作。

void* 类型的指针,如果要使用,需要强制类型转换成目标类型才能进行解引用

2、P[0] = *(p + 0)

int arr[3][2] = {1,2,3,4,5,6};
int* p = arr[0];
printf("%d ",p[0]);
//这里的P[0] = *(p + 0),arr[0]表示第一行的首元素的地址,所以int* p = arr[0]; 等于 int* p = &arr[0][0];所以p[0] = *(&arr[0][0] + 0);那么p[0]打印的东西就为1

指针测试

int main()
{
    char* c[] = {"enter","new","point","first"};
    char** cp[] = {c + 3,c + 2,c + 1,c};
    char*** cpp = cp;

    printf("%s\n",**++cpp);  //point
    printf("%s\n",*-- * ++cpp + 3);  //er
    printf("%s\n",*cpp[-2] + 3);  //  st
    printf("%s\n",CPP[-1][-1] + 1);  //ew
}