C语言学习笔记 入门篇-2

前言

本系列内容为程序媛学习C语言时做的笔记。以代码为主,并备注了打印结果以及详尽的解释注释。希望对你有所帮助。
C语言笔记入门篇包含多篇内容,当前位置:第二篇
注:本篇内容较第一篇过多
C语言并不可怕,请沉下心来,耐心就有收获。
在这里插入图片描述

多级指针

void pointerTest() {
    int i = 100;
    int *i_p = &i;//取出i的内存地址,赋值给i_p(一级指针)
    int **i_pp = &i_p;//取出i_p的内存地址,赋值给i_pp(二级指针)
    int ***i_ppp = &i_pp;//三级指针
    int Q = *i_ppp;//取出三级指针(一个地址)对应的值,即:输出二级指针i_pp(&&i)
    printf("i的值为:%d\n", i);
    printf("&i的值为%p\n", i_p);
    printf("&&i的值为%p\n", i_pp);
    printf("&&&i的值为%p\n", i_ppp);
    printf("*i_ppp的值为%p\n", Q);
    /*
     打印:
        i的值为:100
        &i的值为0061FEE4
        &&i的值为0061FEE0
        &&&i的值为0061FEDC
        *i_ppp的值为0061FEE0
     */
}

指针类型 测试int/double

void pointStyle() {
    int i = 1;
    double d = 2;
    int *i_p = &i;
    double *d_p = &d;
    printf("sizeof(i_p)大小为:%d\n", sizeof(i_p));//输出4
    printf("sizeof(d_p)大小为:%d\n", sizeof(d_p));//输出4
    printf("sizeof(i)小为:%d\n", sizeof(i));//输出4
    printf("sizeof(d)大小为:%d\n", sizeof(d));//输出8
}

Q: 指针占用的内存大小是?
int double xxx 的指针 永远都是 4个字节(32位编译器)/ 4*2(64位编译器)

数组与数组指针

void arrTest() {
    //int[] arrr = {3, 4};//这是错误的写法
    int arr[] = {1, 2, 3, 4};

    //测试arr地址
    printf("打印arr:%p\n", arr);
    printf("打印&arr:%p\n", &arr);
    printf("打印&arr[0]:%p\n", &arr[0]);
    /*
        打印arr:      0061FEDC
        打印&arr:     0061FEDC
        打印&arr[0]:  0061FEDC
     */
    //*arr=1
    //arr是一个指针常量 不能修改  &arr是数组的地址 &arr[0] 是数组首元素地址 这三个相等  但是步长不一样
    //sizeof(arr) = 16 , sizeof(&arr) = 4 , sizeof(&arr[0]) = 4
    //步长:相邻两个元素之间开始地址的距离
    // 数组 和 指针 挂钩 , 数组就是一个内存地址
    // 数组的内存地址 == 第一个元素的内存地址  == &arr

    //通过指针打印第三个元素
    int *arrP = arr;//取出元素一的内存地址
    arrP += 2;//挪到元素3的地址
    printf("arr[3]的值:%d\n", *arrP);//取出元素三的内存地址的值

指针遍历arr

//for (int i = 0; i < 4; ++i) 这样写Linux上会报错
    int i = 0;
    for (i = 0; i < 4; ++i) {
        printf("正常遍历取值 %d\n", arr[i]);		//输出1234
        printf("1:指针遍历取值  %d\n", *(arr + i));		//同上
        printf("2:指针遍历取值    %d\n", *(&arr[0] + i));	//同上
        printf("元素%d内存地址:%p\n", arr[i], arr + i);	//打印如下:
    }
    //元素1内存地址:0061FED8
    //元素2内存地址:0061FEDC
    //元素3内存地址:0061FEE0
    //元素4内存地址:0061FEE4
    //析:数组是连续的内存空间(没有断层,有规律) 数组  每次挪动 4个字节 ==> int数组

    //循环时给数组赋值
    int *j_p = arr;
    for (int j = 0; j < sizeof(arr) / sizeof(int); ++j) {	//sizeof(arr)=16   sizeof(int)=4
        *(j_p + j) = 1000 + j;
        //赋值结果:1000 1001 1002 1003
    }
	printf("测试j_p[2]:%d\n", j_p[2]); //打印1002
}

函数指针

void add(int num1, int num2) {
    printf("%d + %d = %d\n", num1, num2, num1 + num2);
}

void mins(int num1, int num2) {
    printf("%d - %d = %d\n", num1, num2, num1 - num2);
}

void operate(void(*method)(int, int), int i1, int i2) {
    method(i1, i2);
}

void methodPointer() {
    void (*call)(int, int);
    call = add;//方法赋值,写成“ call = &add ”也是一样的
    //printf("%p, %p\n", add, &add);//add, &add值是一样的

    operate(call, 1, 2);//输出:1 + 2 = 3
    operate(mins, 3, 2);//输出:3 - 2 = 1
}

函数指针调用的几种写法:

//函数指针声明来接收函数
void test(void(*p)(int, int)) {
    p(9, 9); //省略*

    (*p)(9, 9);
    (p)(9, 9); //函数的上面已经声明就是函数指针,所以可以省略*
}

以下高能预警

为更好的理解以上内容,作者查询了其它博客,并摘录了部分内容以方便读者查阅。
注:本文剩余部分引自其他作者发文,文末赋有原文地址,欲见详情可移步呦~

内存和地址:
(1)内存中每个位置由一个独一无二的地址标识;(2)内存中每个位置都包含一个值。
  下面的例子显示了内存中的5个字的内容。
在这里插入图片描述
  但是记住他们的地址太麻烦了,所以高级语言提供通过名字而不是地址来访问内存位置的功能,下面用名字代替地址:
在这里插入图片描述
  这些名字我们称之为变量。名字和内存位置之间的关联并不是硬件提供的,而是编译器为我们实现的,硬件仍然通过地址访问内存位置。

指针的运算

C的指针的算数运算只包含以下两种形式:
  (1)指针  +/-  整数
  (2)指针  -   指针
  第一种形式只能用于指向数组中的某个元素,和整数相加减就是让指针在数组中前后移动位置。值得注意的是,指针的移动是按数组中的类型决定的,假如数组类型是char类型,指针加一表示向后移动一个字节。而在int类型的数组中,指针加一是移动四个字节,并非一个,这个注意区分。
  第二种形式的条件式两个指针都指向同一个数组,相减的结果是两个指针在内存中的距离。加入一个float类型的数组,每个类型占4个字节。如果数组的起始位置是1000,指针p1的值是1004,p2的值是1024。则p1-p2的结果是5。因为两个指针的差值(20)将除以每个元素的长度(4)。
  对于指针的关系运算有:
  <   <=  >  >=
  不过前提是它们都指向同一个数组中的元素。

指针和数组的区别/联系

int a;
a = 3;
编译器为了完成这两句代码,首先在编译过程中要创建一个符号表,样子大概如下图:
在这里插入图片描述
然后在运行过程中,编译器发现a=3这句代码时,会在符号表里找a对应的地址,然后把3放入对应的地址,即这里的0x1000。那如果是一个指针呢?即如果是*p=3会怎么做呢?首先,符号表变成了这个样子
在这里插入图片描述

在运行过程中,编译器遇到p=3时,首先要从符号表中找到p的地址0x1004,然后取出0x1004中的内容,这里假设为0x2000,最后把3放到0x2000内存地址中,即(0x2000)=3。

相信大家已经看明白了,想比于利用普通变量,利用指针存取数据的过程中多了一部取地址的的过程。这也就是指针变量于普通变量最大的不同。

下面再来看一下指针加偏移量的引用方式,还以上面的指针p为例,让我们来看一下*(p+2)=3的实现过程。首先,编译器从符号表中找到p然后,取出里面的内容0x2000,再根据其类型(int*),做一个运算,0x2000+2×sizeof(int)=0x2008。所以编译器会把3放入0x2008这个内存地址。整个过程可表示为*(*(0x1004)+2)=3。从这里也可以看出为什么指针必须有类型,因为在引用过程中要用到指针所指类型的长度。

最后来看一下,数组元素的引用是如何实现的,假设我们定义了一个数组,并对其元素进行了引用
int b[10];
b[4] = 3;
对应的符号表变成了这个样子
在这里插入图片描述

那b[4]=3如何完成呢?首先,找到符号b,然后发现其类型为int[](假想表达方式,C语言中不支持这样写),所以计算式变成了0x1008 + 4×sizeof(int)=0x1018,然后把3放入0x1018就可以了。用一个式子表达就是*(0x1008+4)=3。

从上面的寻址式子可以看出,普通变量、指针、数组三者对于编译器的区别。具体到数组,它即具有普通变量的直接性,即不用取两次地址里的内容而是取一次,同时又具有和指针相同的偏移量引用方式,即下标的实现实际是由指针加偏移量实现的。

为了表明上述事实(或者是为了提高C语言入门门槛),C语言对指针与数组的引用方式做了可以“交叉”使用的语法规定。就上面的例子来说,如果p指针指向数组b时,b[i]、(b+i)、p[i]、(p+i)都是对数组第i个元素的正确引用方式,这也给很多C语言学习者制造了“指针和数组一样”的错觉。

在函数形参中的表现

在向函数传递参数时,如果实参是一个一维数组,那用于接受的形参为对应的指针。也就是传递过去是数组的首地址而不是整个数组。这么做的原因主要是效率,这是无可争议的,但为了使用上的简便与通用,C语言接受两种形参的写法,即下面两种写法是相同的
int foo(int *a, int n);
int foo(int a[], int n);
甚至是
int foo(int a[20], int n);
在底层都是完全一样的形式。这简化了C语言的使用,但同时也增加了对其进一步理解的难度。正因为很多程序稀里糊涂就通过了,所以程序员就会一直稀里糊涂下去。至于哪种写法好的争论,只要你理解了,用哪种写法到是不必强求。指针形式表明了传参过程的实质,而数组形式表明了这个指针对应一个数组实参,甚至后面的数字可以提供数组长度的参考。

摘录部分出处

本文后半部分引自以下两篇文章,在此感谢两位巨人的肩膀。

https://blog.csdn.net/cyfcsd/article/details/54773355
https://www.cnblogs.com/maluning/p/7955648.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值