C语言指针

目录

一、相关定义

1.内存和地址

1.1内存

1.2地址与编址

2.指针变量和地址

2.1取地址操作符(&

2.2指针变量和解引用操作符(*)

2.3指针变量的大小

3.指针变量类型的意义

3.1指针的解引用

3.2指针+-整数

3.3void*指针

4.const修饰指针

4.1const修饰指针变量

5.指针运算

6.野指针

6.1野指针成因

6.2如何规避野指针

7.assert断言

8.指针的使用和传址调用

二、指针与数组

1.数组名的理解

2.使用指针访问数组

3.一维数组传参的本质

4.二级指针

5.指针数组

6.指针数组模拟二维数组

7.1数组指针变量的定义

7.2数组指针变量的初始化

8.二维数组传参的本质 

三、指针与函数

1.函数指针变量

2.函数指针数组

3.转移表

4.qsort函数的模拟实现


一、相关定义

1.内存和地址
1.1内存

内存空间的管理是将内存划分为一个个的内存单元,每个内存单元的大小取一个字节;一个字节可以存放八个比特位,每个比特位可以存储一个2进制的1或0;每个内存单元有一个编号,通过这个编号可以迅速找到一个内存空间。

在计算机中将内存单元的编号称为地址,C语言中给地址起名为指针

也就是说,内存单元的编号==地址==指针

1.2地址与编址

CPU和内存之间的数据交互是通过地址总线来实现的。32位机器有32根地址总线,每根线有两种状态,表示0和1,即可表示两种含义,32根地址总线就能表示2^32种含义,每一种含义都代表一个地址。

2.指针变量和地址
2.1取地址操作符(&
#include <stdio.h>
int main()
{
    int a = 10;
    return 0;
}

上述代码创建了一个整型变量a,向内存中申请了一块空间,想要得到a的地址,则需要使用取地址操作符(&)

2.2指针变量和解引用操作符(*)

2.2.1指针变量

通过取地址操作符(&)拿到的地址需要存储时,就要使用到指针变量

例如

#include <stdio.h>
int main()
{
    int a = 10;
    int* pa = &a;
    return 0;
}

这段代码的作用就是,取出a的地址并存储到指针变量pa中

指针变量也是一种变量,只不过存放的是地址

2.2.2解引用操作符

指针变量中存放的地址需要使用时,则需要使用解引用操作符(*)

#include <stdio.h>
int main()
{
    int a = 100;
    int* pa = &a;
    *pa = 0;  //这句就是通过pa中存放的地址找到指向的空间,将a中的内容改成了0
    return 0;
}
2.3指针变量的大小

指针变量的大小取决于地址的大小,32位平台下地址是32个bit位(4个字节);64位平台下地址是64个bit位(8个字节)

注意指针变量的大小和类型是无关的,只要是指针类型的变量,在相同的平台下,大小都是相同的

3.指针变量类型的意义
3.1指针的解引用

指针的类型决定了对指针解引用的时候有多大的权限(一次能操作几个字节)

例如:char*的指针解引用只能访问一个字节,而int*的指针的解引用能访问四个字节

3.2指针+-整数
#include <stdio.h>
int main()
{
    int n = 10;
    char* pc = (char*)&n;
    int* pi = &n;
    printf("%p\n", &n);
    printf("%p\n", pc);
    printf("%p\n", pc+1);
    printf("%p\n", pi);
    printf("%p\n", pi+1);
    return 0;
}

运行结果为

&n   = 00AFF974
pc   = 00AFF974
pc+1 = 00AFF975
pi   = 00AFF974
pi+1 = 00AFF978

char*类型的指针变量+1跳过一个字节,int*类型的指针变量+1跳过了4个字节

结论:指针的类型决定了指针向前或者向后走一步有多大(距离)

3.3void*指针

void*指针可以理解为无具体类型的指针(泛型指针),这种指针可以用来接受任意类型地址,但不可以直接进行指针的+-整数和解引用的运算

4.const修饰指针
4.1const修饰指针变量
#include <stdio.h>
void test1()
 {
     int n = 10;
     int m = 20;
     int *p = &n;
     *p = 20;//ok?
     p = &m; //ok?
 }
 void test2()
 {
    int n = 10;
    int m = 20;
    const int* p = &n;
    *p = 20;//ok?
    p = &m; //ok?
 }
 void test3()
 {
     int n = 10;
     int m = 20;
     int *const p = &n;
     *p = 20; //ok?
     p = &m;  //ok?
 }
 void test4()
 {
     int n = 10;
     int m = 20;
     int const * const p = &n;
     *p = 20; //ok?
     p = &m;  //ok?
 }
 int main()
 {
     //测试⽆const修饰的情况
     test1();
     //测试const放在*的左边情况
     test2();
     //测试const放在*的右边情况
     test3();
     //测试*的左右两边都有const 
     test4();
     return 0;
 }

结论:const修饰指针变量的时候

  • const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变,但是指针变量本身的内容可以改变。
  • const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

5.指针运算
  • 指针+-整数
  • 指针-指针
  • 指针的关系运算
6.野指针

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

6.1野指针成因
  1. 指针未初始化
  2. 指针越界访问
  3. 指针指向的空间释放
6.2如何规避野指针

6.2.1指针初始化

明确知道指针指向哪里时直接赋值地址,不知道指针应该指向哪里,可以给指针赋值NULL(NULL是C语言中定义的一个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错)

6.2.2小心指针越界

一个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问

6.2.3指针变量不再使用时,及时置NULL,指针使用之前检查有效性

6.2.4避免返回局部变量的地址

7.assert断言

assert.h头文件定义了宏assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行,这个宏称为“断言”。如果已经确定程序没有问题,不需要再做断言,就在#include<stdio.h>语句的前面,定义一个宏NDBUG

8.指针的使用和传址调用

使用传值调用时实参的数值不会改变,这时就需要采用传址调用

#include <stdio.h>
 void Swap(int*px, int*py)
 {
    int tmp = 0;
    tmp = *px;
    *px = *py;
    *py = tmp;
 }
 int main()
 {
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("交换前:a=%d b=%d\n", a, b);
    Swap(&a, &b);
    printf("交换后:a=%d b=%d\n", a, b);
    return 0;
 }

传址调用可以让函数和函数主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量;所以函数中只是需要主调函数中的变量值来实现计算,就可以采用传址调用;如果函数内部要修改主调函数中的变量的值,就需要传址调用。

二、指针与数组

1.数组名的理解

数组名就是数组首元素(第一个元素)的地址,但是有两个意外

  • sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
  • &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
2.使用指针访问数组
 #include <stdio.h>
 int main()
 {
    int arr[10] = {0};
    int i = 0;
    int sz = sizeof(arr)/sizeof(arr[0]);
    int* p = arr;
    for(i=0; i<sz; i++)
    {
        scanf("%d", p+i);
    }
    for(i=0; i<sz; i++)
    {
        printf("%d ", *(p+i));
    }
    return 0;
}
3.一维数组传参的本质

数组传参本质上传递的是数组首元素的地址

结论:一维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

4.二级指针

存放指针变量的地址的指针变量就是二级指针

#include <stdio.h>
int main()
{
    int a = 10;
    int* pa = &a;
    int** ppa = &pa;
    return 0;
}

ppa即为二级指针

5.指针数组

存放指针的数组

6.指针数组模拟二维数组
#include <stdio.h>
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[3] = {arr1, arr2, arr3};
    int i = 0;
    int j = 0;
    for(i=0; i<3; i++)
    {
        for(j=0; j<5; j++)
        {
            printf("%d ", parr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

以上代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的
7.数组指针变量 

7.1数组指针变量的定义
int (*p)[10];

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

7.2数组指针变量的初始化
int arr[10] = {0};
int (*p)[10] = &arr;
8.二维数组传参的本质 

二维数组传参的本质是传递了地址,传递的是第一行这个一维数组的地址,形参也可以写成指针形式的

三、指针与函数

1.函数指针变量
#include <stdio.h>
int Add(int x, int y)
{
    return x+y;
}
int main()
{
    int(*pf3)(int, int) = Add;
    printf("%d\n", (*pf3)(2, 3));
    printf("%d\n", pf3(3, 5));
    return 0;
}

 通过函数指针调用指针指向的函数

2.函数指针数组

int (*)()类型的函数指针

int (*a[3])();
3.转移表
#include <stdio.h>
int add(int a, int b)
{
   return a + b;
}
int sub(int a, int b)
{
   return a - b;
}
int mul(int a, int b)
{
   return a*b;
}
int div(int a, int b)
{
   return a / b;
}
int main()
{
   int x, y;
   int input = 1;
   int ret = 0;
   int(*p[5])(int x, int y) = { 0, add, sub, mul, div };
   do
   {
       printf("*************************\n");
       printf("  1:add           2:sub  \n");
       printf("  3:mul           4:div  \n");
       printf("  0:exit                 \n");
       printf("*************************\n");
       printf( "请选择:" );
       scanf("%d", &input);
       if ((input <= 4 && input >= 1))
       {
           printf( "输⼊操作数:" );
           scanf( "%d %d", &x, &y);
           ret = (*p[input])(x, y);
           printf( "ret = %d\n", ret);
       }
       else if(input == 0)
       {
           printf("退出计算器\n");
       }
       else
       {
           printf( "输⼊有误\n" );        
       }
    }while(input);
    return 0;
}
 

转移表实现简单的计算器

4.qsort函数的模拟实现
#include <stdio.h>
#include <stdlib.h>
int compare(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}
void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[] = { 3,6,8,0,2,5,8,7 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), compare);
	print(arr, sz);
	return 0;
}

  • 13
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值