C语言指针知识全覆盖

#内存、地址、指针

计算机里数据存储在内存单元,而每一个内存都有唯一的编号,这个编号也叫地址,在C语言里,一个指针就相当于一个地址,用户可以通过指针准确的访问一块内存的内容,也可以通过指针改变这块内存的内容。

#指针操作符&,*

  • &:取地址操作符,用于取出一个变量/常量的地址存放到指针变量里(只能接受同类型的变量/常量的地址)。
  • *:解引用操作符,用于取出指针变量指向的内存空间里的数据(内容)。

 #指针变量的类型对其大小和解引用的影响

  • 指针变量的类型:整型指针、字符指针、浮点型指针、数组指针、函数指针、二级指针(存储的是一级指针的地址)、指针数组(数组的每一个元素都是一个指针)等。
  • 指针变量的大小:无论何种类型的指针,其大小都只取决于计算机操作系统的位数,在32位环境下,指针的大小都为4个字节,而在64位系统下,指针大小为8个字节。
int* p = 1;
printf("%d ", sizeof(p));//32位环境输出4,64位环境输出8
  • 指针类型的意义:指针的操作可以精确到一个字节,而不同的指针类型决定了一次可以操作多少个字节,影响到指针的加减等操作。

 举例1.char*的指针一次向后移动一个字节,而int*一次移动4个字节,*是解引用,b1+1是元素a[1]的地址,b2+1是元素a[4]的地址。

             2.指针-指针等于两个地址间的元素个数,若要计算之间的字节数,可以将指针强制转化为char*再相减。

             

        ##void型指针

void型指针:可以接收任意类型变量/常量的地址,却不能直接进行整形加减,解引用的操作。但可以用于函数参数的传参,这种应用在文章后半段有涉及。

        ##const修饰指针变量

const:通常用来修饰变量、数组等,让其在代码运行时不被改变 。也可用来修饰指针变量,有两中效果不同的修饰方式。

  1.     const在 * 左侧:如 int const* a=&a1(效果、用法都等同于const int* a=a1),此时指针变量的内容( a )可以被改变,但指针指向的内容(a1的值)不可以被改变。

     2.     const在 * 右侧:如int* const b=&b1,此时指针变量的内容(  b  )不可以被改变,但指针指向的内容(  b1 )可以被改变。

                        总结:指针变量——>指针变量的内容—(@)—>指针指向的内容 , * 在const哪一侧,就可以改变(@)哪一侧。

        ##野指针

野指针:指向的位置是未知的、随机的、连写代码的本人都不知道的

造成野指针的原因指针变量未初始化越界访问指向的空间释放

  •                                  指针变量未初始化:未初始化,指针变量的内容将为随机值。
  •                                 越界访问:指针的操作超出数组的边界
int a1[]={1,2,3,4,5};
int* a=a1;
a=&a[0]+100;//此时a指向的地址已不在a1的范围内,a为野指针
  •                                  指向的空间释放:
int add(int a1, int a2){
    int n=a1+a2;
    return &n;
}
int main(){
    int* b=add(6,6);//n为局部变量,在add执行完毕后,为n创建的空间被销毁
                    //因此赋给b的是一个未知的地址,b为野指针。
 }

 如何避免野指针:在初始化时可选择初始化为NULL、避免越界访问、不将局部变量的地址赋给指针、使用指针后赋值为NULL同时下次使用前判断是否为NULL。

int* a=NULL;

        ###assert 断言

assert:(使用会一定程度上增加程序运行时间)用法多样,可用于预防可能出现的程序错误,一般在Debug调试版本里使用,用于作者改进程序,Release是发布版本,供给用户使用,不会用到。使用需包含头文件#include<assert.h>;

assert(expression),若expression为假 ,程序终止(使用assert的一个好处是可以知道错因、在代码的哪一行出错);expression为真,程序继续运行。(下图为assert报错样例

如何禁用assert:需在头文件 #include<assert.h>前面的代码定义一个宏 #define NDEBUG,如此便可禁用程序里所有的assert断言语句。

       ##二级指针

二级指针:二级指针存储的是一个指针变量的地址, 二维数组对 & 、* 的用法同一维数组。 可用于动态内存分配、函数参数传递、链表操作、指向指针的数组。

          ##传值调用

将实参的值传给函数,对形参的操作无法改实参的值,但可以通过指针间接改变实参的值


#数组名是数组首元素的地址

数组名是数组首元素的地址:如  int arr[ ]={1,2,3} ,  “arr” 便是数组arr的首元素( arr[0] )的地址。

                                                有两个特例:sizeof(arr),&arr里的“arr”表示的是数组的地址。

所以在将一维数组作为参数传参时,实际上传的是一维数组的首元素地址。

因此可以用指针完成对数组的任何操作,需要注意的是 a[i] 等同于 *(a+i),这对于指针后面的二维数组、指针数组等的使用也尤其重要 :例如遍历数组

#字符型指针

字符型指针:字符指针不仅可以指向一个字符,也可以指向一个字符串,如:

​
char a="sdfdf";
char* a1=&a;
a的地址给了a1,但实际上是将字符串第一个字符‘s’的地址给了a1

​

#指针数组、数组指针

        ###二维数组

一维数组在内存中连续存放,二维数组也同样是的,下列例子中二维数组(arr)在第二行首元素地址(arr[1][0])紧跟第一行(arr[0]1])后面。而二维数组的数组名(arr)是第一行的地址,arr[4][2]每一行都相当于一个有2个元素的数组,因此(arr[0])也是(数组arr)的首元素地址,(arr[1])是(数组arr)的第二行地址,以此类推。

而二维数组的数组名(arr)是第一行的地址,arr[4][2]每一行都相当于一个有四个元素的数组,因此(arr[0])也是(数组arr)的首元素地址,(arr[1])是(数组arr)的第二行地址,以此类推。 

指针数组:是数组的一种类型,格式是 类型* 变量名 [ ]  ,这种数组的每一个元素都是指针,而且指针的类型必须同数组的类型相同,如 int* p[2] ,数组p的两个元素都是整型指针。

指针数组模拟实现二维数组:

  int arr1[2] = { 1,2 };
  int arr2[2] = { 3,4 };
  int* p[2] = { arr1,arr2 };
  //p[0]是一个指针,这样初始化相当于 p[0]=arr1;
  for (int i = 0; i < 2; i++) {
      for (int j = 0; j < 2; j++) {
          printf("%d ", p[i][j]);
        //前面提到*(p+i)等同于p[i]
        //因此也可以写为*(p[i]+j)/*(*(p+i)+j);
      }
  }

数组指针:是指针的一种类型。格式是 类型 (*变量名) [ ] ,形如 int (*p) [2] ,p是一个指向包含2个元素的整型数组的指针,也只能接受这种类型,否则会报错。

 引用于二维数组的传参

#include<stdio.h>
void print1(int (*p1)[2], int n){
    for(int i=0; i<2; i++){
        for(int j=0; j<n; j++){
            printf("%d ",*(*(p+i)+j));
        }
    }

}
int main(){
  int arr[2][2] = { 1,2,3,4};
  int(*p)[2] = arr;//赋给数组指针的是二维数组arr第一行的地址,
                     //第一行相当于一个两个元素的一维数组,因此是合法的
  print1(arr,2);
    return 0;
}

有一个小知识点:两个不同的字符指针被两个一模一样的字符串赋址, 他们被赋址的地址是同一个地址,这有助于节省空间,而单独创建两个一模一样的字符串,它们的地址却不是同一个。

#函数指针

函数指针:指向一个函数的指针。函数的函数名就是函数的地址,格式是:(返回类型)(*指针变量)(参数1,参数2......)。函数指针只能指向一个返回类型、参数类型相同的函数,如int (*p) (int, int),该指针只能接受一个返回类型为int型且接受两个整型参数的函数(例如:int p1 (int x, int y))。

  int (*p) (int)//定义函数指针时(*p)的()不能漏掉 

        void (*signal(int, void(*) (int) ) (int):这是一个函数声明,同指的是signal函数接受两个参数,一个是int,一个是函数指针(其指向的函数返回类型为void,参数是一个int),返回另一个函数指针(向的是一个void (*) (int)型的函数)。 

        可以用【(void (*) (int)) signal (int,void (*) (int))】(但是是一个不合法的表达)帮助理解。

 

 #函数指针数组

函数指针数组:数组元素都是同类型的函数的地址,如 int (*p[2]) (int, int), 每一个元素都是一个返回类型为int ,参数为两个int的函数的地址。若将int (函数名)(int)的函数地址存入其中就是不合法的。

#指针的综合应用>>qsort函数

qsor函数:qsort函数是C语言标准库中的一个排序函数,它使用快速排序算法对数组进行排序,需包含头文件<stdlib.h>。在以下代码中,综合应用了指针部分的知识,void*作为能接受任何类型的指针,它是模拟实现qosrt函数的关键,通过回调函数的使用以及对void*型指针的强制类型转换,将其转换整型int*、字符型char*甚至结构体型指针,达到了对任意类型数据的排序。

               以下源码有多个模拟,因此有多个main函数,进行测试请分模块单独进行尝试

#include<stdio.h>
#include<string.h>
#include<stdlib.h>

///冒泡排序
void sort(int arr[], int n){
    int i = 0;
    for (i = 0; i < n - 1; i++){
        int j = 0;
        for (j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}
void print(int arr[], int sz) {
    for (int i = 0; i < sz; i++)
        printf("%d ", arr[i]);
}
int main() {
    int arr[] = { 1,5,7,3,8,6,0,4,9,2 };
    int n = sizeof(arr) / sizeof(arr[0]);
    sort(arr, n);
    print(arr, n);
    return 0;
}


/qosrt函数排序结构体
struct p
{
    char ab[12];
    int cd;
};
int  Cmp_num(const void* p1, const void* p2){//排序字符串结构体成员cd
    return ((struct p*)p1)->cd - ((struct p*)p2)->cd;//成员选择操作符”.“用于访问对象的成员变量或成员函数,
}                                                                               //而成员访问操作符"  -> "用于访问指针所指向的对象的成员变量或成员函数。
int  Cmp_char(const void* p1, const void* p2){//排序数字结构体成员ab
    return strcmp(((struct p*)p1)->ab, ((struct p*)p2)->ab);
}
int  Cmp(void* p1, void* p2){排序数组
    return (*(int*)p1 - *(int*)p2);
}
int main(){
    //升序排序
    struct p s[] = { {"qweg",12},{"qwee",11} };
    int arr[] = { 2,1,4,7,9,8,0,3,5,6 };
    qsort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), Cmp_char);//排序结构体字符串成员
    qsort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), Cmp_num);//排序结构体数字成员
    qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), Cmp);
    printf("%s %s %d %d\n", s[0].ab, s[1].ab, s[0].cd, s[1].cd);
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
        printf("%d ", arr[i]);
    return 0;
}


///基于冒泡排序算法模拟qsort函数排序结构体;
struct p{
    char ab[12];
    int cd;
};
int  Cmp_num(const void* p1, const void* p2){
    return ((struct p*)p1)->cd - ((struct p*)p2)->cd;//强制类型转换
}
int  Cmp_char( const void* p1, const void* p2){
    return strcmp(((struct p*)p1)->ab, ((struct p*)p2)->ab);//强制类型转换
}
void Swap(void* num1, void* num2, int n){
    int i = 0;
    for (i = 0; i < n; i++){
        char temp = *((char*)num1 + i);
        *((char*)num1 + i) = *((char*)num2 + i);
        *((char*)num2 + i) = temp;
    }
}
void bubble_sort(void* base, int count, int types, int (*cMp)(void*, void*)){
    int i = 0;
    for (i = 0; i < count - 1; i++){
        int j = 0;
        for (j = 0; j < count - i - 1; j++)
            if (cMp((char*)base + j * types, (char*)base + (j + 1) * types) > 0)
                Swap((char*)base + j * types, (char*)base + (j + 1) * types, types);
    }
}
int main(){
    struct p s[] = { {"qwex",12},{"qweg",11},{"qweb",15} };
    bubble_sort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), Cmp_char);//排序结构体字符成员
    //bubble_sort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), Cmp_num);//重复调用会覆盖前一次的排序!
    struct p s1[] = { {"qwex",12},{"qweg",11},{"qweb",15} };//为了观察到分别对字符和数字成员的排序,这里拷贝一份结构体
    bubble_sort(s1, sizeof(s1) / sizeof(s1[0]), sizeof(s1[0]), Cmp_num);//排序结构体数字成员
    printf("%s %s %s ", s[0].ab,s[1].ab, s[2].ab);
    printf("%d %d %d", s1[0].cd, s1[1].cd, s1[2].cd);

    return 0;
}

/基于冒泡排序算法模拟qsort函数排序整形数组
int  Cmp(void* p1, void* p2){
    return (*(int*)p1 - *(int*)p2);//强制类型转换
}
void Swap(void* num1, void* num2, size_t n){
    int i = 0;
    for (i = 0; i < n; i++){
        char temp = *((char*)num1 + i);//细化到一个字节一个字节的互换,这样做的好处是可以对任何类型的数组进行数值交换来进行排序
        *((char*)num1 + i) = *((char*)num2 + i);//不管是4个字节的int,还是8个字节的double,对于细化到一个字节的char*操作来说都
        *((char*)num2 + i) = temp;                          //绰绰有余。
    }
}
void bubble_sort(void* base, size_t count, size_t types, int (*cMp)(void*, void*)){// count是元素个数,types是一个元素的大小
        int i = 0;                                                                                                          
        for (i = 0; i < count - 1; i++){
            int j = 0;
            for(j=0; j<count-i-1; j++)
                 if(cMp((char*)base+j*types,(char*)base+(j+1)*types)>0)// j*types代表第数组arr的j-1个元素
                     Swap((char*)base + j * types, (char*)base + (j + 1) * types,types);
    }
}
void Print(int arr[], size_t sz){
    for (int i = 0; i < sz; i++)
        printf("%d ", arr[i]);
}
int main(){
    int arr[] = { 1,5,7,3,8,6,0,4,9,2 };
    bubble_sort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(int), Cmp);//排序整形数组
    Print(arr, sizeof(arr) / sizeof(arr[0]));
    return 0;
}

以上便是C语言指针部分的大部分知识,可能忽略了一些细微部分,请谅解。如有错误,请读者指出,我将及时改正!! 

  • 33
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值