辨析C语言的指针

285 阅读9分钟

学习C和C++对于Android程序员学习NDK来说,非常重要,今天就从手撕HelloWord!开始C和C++的学习!希望对兄弟们有点用处!

1、main函数的结构体

#include <stdio.h>
//<>寻找系统的资源
// "" 寻找我们自己写的资源
//.h .hpp (声明文件 头文件)
// .c .cpp(实现文件)

//代码结构和 基本数据类型
int main(void) { //主函数入口
    printf("Hello, World!\n"); 
    return 0;
}

2、常用基本数据类型和printf打印

在C语言中,使用控制台打印和Java的有所不同,打印变量,需要使用占位符进行占位,有点类似于,在写SQL的时候,? 进行占位

int mainT1(void) { //主函数入口
    int numberInt =100;
    double nuberDouble =200;
    float numberFloat =100;
    long numberLong = 300;
    short numberShort=100;
    char c = 'a';
    char * str= "一杯凉白开";
    //printf(numberInt);//这样是打印不出来的,c打印需要占位符
    printf("numberInt的值:%d\n",numberInt);
    printf("nuberDouble的值:%lf\n",nuberDouble);
    printf("numberFloat的值:%f\n",numberFloat);
    printf("numberLong的值是%ld\n",numberLong);
    printf("numberShort的值:%d\n",numberShort);
    printf("c的值%c\n",c);
    printf("str的值%s\n",str);


    //基本数据类型所占字节数
    printf("int 数据类型所占的字节数:%d\n",sizeof(int));
    printf("double 数据类型所占字节数:%d\n",sizeof(double));
    printf("float 数据类型所占字节数:%d\n",sizeof(float));
    printf("long 数据类型所字节数:%d\n",sizeof(long));
    printf("short 数据类型所占字节数:%d\n",sizeof(short));
    printf("char 数据类型所占字节数:%d\n",sizeof(char));
   
    return 0;
}

运行结果

image.png

3、C和C++ 万物皆指针 辨析 指针,指针变量 和内存地址

#include <stdio.h> //标准库
// C C++(对象) 万物皆指针
//Java 万物皆对象
//Linux 万物皆文件
int main(void) {
    //指针 == 地址
    //%p 地址输出的占位
    //&number 取出number 的地址
    int number = 1000; // 类型为int   别名为number 的变量 值 为100
    printf("number变量的地址:%p\n",&number);//输出的地址是 74565ffc1c
    //方法二 通过地址去取值
    //*地址,取出该地址的里的内容
    printf("number的值:%d\n",*(&number));
    int * intp = &number;//类型为 int * (int类型的指针)  别名为 intp  的值是 number 的内存地址74565ffc1c
    printf("number的值为:%d\n",*intp);
    return NULL;
}

image.png

为了方便大家理解,画上一个图

image.png

int number =1000 在内存区域,开辟一块空间, 存放一个1000,这一块的地址是74565ffc1c
&number 取出这块空间的地址 74565ffc1c
*(&number)先执行括号里边的,拿出这块空间的地址,在执行外边的 拿出这个地址所在空间的值 1000
int * intp = &number; 在内存区域开辟另一块空间,内存地址为0x356 这个空间只能存放 存放了int值的内存地址

4、交换两个变量的值

void change(int i);
int mainT5() {

    int number= 100;
    int * p =&number;
    *p =200;
    change(number);
    printf("%d",number);
    return 0;
}
//函数不能写在main的下边,面向过程的,在main下边,main中调用的时会找不到,如果非要写在下边,需要先声明,
 void change(int i) {
     i =200;
 }

这样写是改变不了number的值的,在change函数进栈时,int i 新的内存地址,值是number 的值,相当于number 的副本,你在改变change里边的i的时候,只是改变新内存地址的值,和number并没有半毛钱关系

# include <stdio.h>
void change();//C不支持函数重载,所以在声明函数时,可以不写形参
int main() {
    int a = 100;
    int b =200;
    change(&a,&b);//传入 a的内存地址,和b的内存地址
    printf("a的值:%d,b的值:%d",a,b);
    return 0;
}

void change(int* a , int* b) {
    int temp =* a;//拿到*a的值赋值给temp
    *a = *b; //拿到b的值赋给 a
    *b = temp;//将temp 赋值给b
}

所以想要通过方法改变,变量的值,我们需要传入指针,也就是内存地址,然后在方法内通过内存地址改变里边的内容,上面的代码不好理解的话,就看图

image.png

5、多级指针

指针存放的是内存地址,但是自己也有内存地址

#include <stdio.h>

int main(void) {
   int number = 100;
    //一级指针
    int * number_p = &number;
    //二级指针 在平时开发中,我们最多用到三级指针,如果说他开发了一东西,用到了八级指针,那就是在装逼哈哈
    int * * number_p_p =&number_p;
    int * * * number_p_p_p =&number_p_p;
    //分别打印上面两个指针
    printf("number_p:%p\n",number_p);
    printf("number_p_p:%p\n",number_p_p);
    printf("number_p_p_p:%p\n",number_p_p_p);

    //如果我想打印 number的值,可以 这样打印
    printf("一级指针获取number的值:%d\n",*number_p);
    printf("二级指针获取number的值:%d\n",**number_p_p);
    printf("三级级指针获取number的值:%d\n",***number_p_p_p);
    return 0;
}

image.png 这一段代码,能帮助我们更好的理解多级指针
int number = 100
别名为number,类型为int ,值为100,内存地址是 78f2bffa14

int * number_p =&number
别名为number_p,类型为int型的一级指针,值为78f2bffa14 内存地址 是78f2bffa08

int ** number_p_p =&number_p

别名为number_p_p,类型为int型的二级指针,值为78f2bffa08 内存地址是78f2bffa00
**number_p_p
*number_p_p 拿出 内存地址为 78f2bffa08 的值 78f2bffa14
**number_p_p 拿出内存地址为 78f2bffa14 的值 100

6、数组与数组指针

#include <stdio.h>
//数组与数组指针
int main() {
    //定义数组
    //int [] arr ={1,2,3,4}; 这是java的写法,和C并不一样
    int arr [] ={1,2,3,4};
    //遍历数组 这种写法,在Clion上不报错,在Linux上会报错
    // for (int i = 0; i < 4; ++i) {
    //
    // }
    //比较规范的写法
    int i = 0;
    for ( i = 0; i < 4; ++i) {
        printf("%d\n",arr[i]);
    }
    //数组指针
    //数组的内存地址  == 数组里第一个元素的内存地址  打印arr 输出的也是 arr的内存地址
    printf("数组%p\n",arr);
    printf("数组的内存地址%p\n",&arr);
    printf("数组第一个元素的内存地址%p\n",&arr[0]);
    //挪动指针
    int * arrp = &arr;
    //数组第一个值
    printf("数组第一个元素的值:%d  内存地址:%p\n",*arrp ,arrp);
    //输出数组第二个值,挪动指针
    arrp ++;
    printf("数组第二个元素的值:%d  内存地址:%p\n",*arrp,arrp);
    arrp ++;
    printf("数组第三个元素的值:%d  内存地址:%p\n",*arrp,arrp);
    arrp ++;
    printf("数组第四个元素的值:%d  内存地址:%p\n",*arrp,arrp);
    arrp -= 3;
    printf("数组第一个元素的值:%d  内存地址:%p\n",*arrp,arrp);
    arrp +=1000;//超出数组长度并不会报错,这是一个野指针,他的值是个系统值
    printf("超出数组长度:%d\n",*arrp)

}

image.png 在C语言里,数组的内存地址是数组第一个元素的内存地址,打印arr 也是数组的内存地址,然而数组的内存地址是数组第一个元素的内存地址,所以他们三个打印出来时相同的。
打印arr 也是数组的内存地址 这句话和java里有点像,java打印数组或者对象的时候,也是打印的一个地址,如果想打印具体的值,还得重写对象的toString方法。
在我们写代码的过程中,发现指针是有类型的,int * ,double *,他们的大小都是一样的,指针占用内存大小 32位4字节,64位是8字节,既然大小都一样,为什么要区分类型呢?
移动指针的时候,指针+1,是移动到下个元素的地址,四个元素的地址打印出来了,大家是否发现了他们的规律,后一个都比前一个大4,然而int的大小就是4个四节,如果把数组的类型换成double,打印出来的地址也是这样的规律,后一个元素的内存地址,比前一个大8,double的大小也是8个字节。所以指针挪动的是根据指针类型进行挪动的,所以我们在循环的时候可以这样写

int main() {
   int arr [4];
   int * arrp =&arr;
   int arrLength = sizeof(arr)/sizeof(int);
   int i =0;
   for (i = 0; i < arrLength; ++i) {
      *(arrp+i) = 10+i;
      printf("arr[%d]:%d\n",i,arr[i]);
   }
}

7、函数指针

函数指针,函数也是有指针的,其用法类似Java里的接口回调,老规矩,先上例子,

# include <stdio.h>
void add(int a,int b) {
    printf("a+b的值为:%d",a+b);
}
void mins(int a ,int b) {
    printf("a-b的值为:%d",a-b);
}
// 操作 回调到 add,mins
//void(*method)(int,int)   void 返回值 (*method) 函数名字, (int,int)两个形参
caluteNumber(void(*method)(int,int), int number1,int number2) {
    method(number1,number2);//回调传进来的方法
}
int main () {
    caluteNumber(add,10,20);
    caluteNumber(mins,20,10);
    return  0;
}

通过例子,我们通过函数指针的方式,去回调了两个不同的方法!下边我们分别打印方法,和方法的地址

# include <stdio.h>
void add(int a,int b) {
    printf("a+b的值为:%d\n",a+b);
}
void mins(int a ,int b) {
    printf("a-b的值为:%d\n",a-b);
}
// 操作 回调到 add,mins
//void(*method)(int,int)   void 返回值 (*method) 函数名字, (int,int)两个形参
caluteNumber(void(*method)(int,int), int number1,int number2) {
    method(number1,number2);//回调传进来的方法
    printf("method :%p\n",method);
    printf("&method:%p\n",&method);
    printf("add:%p\n",add);
    printf("&add:%p\n",&add);
    printf("mins:%p\n",mins);
    printf("&mins:%p\n",&mins);
}
int main () {
    caluteNumber(add,10,20);
    caluteNumber(mins,20,10);
    return  0;
}

image.png 首先, add 和 &add的值是一样的,mins 和&mins的值是一样的,和数组有些类似,数组 是 arr 和 &arr 是一样的事数组首元素的地址,所以方法就是个指针,(先这样理解)
void(*method)(int,int) 他是个指针 存放的是方法的地址,所以打印出来methd的值是add的地址,然后这个指针,也有它自己的地址 00000075075ffd40

8、总结

从刚刚的体验来说,C(万物皆指针)和Java(万物皆对象)还是有很大的区别的,在以后得工作中,我们要经常和指针打交道,所以指针还是挺重要的,我们辨析了什么是指针,指针变量,多级指针,数组指针,和函数指针!