指针 [2] —— 指针与数组、函数的结合以及类型修饰符Volatile

原创首发于CSDN,转发请注明出处,谢谢!
https://blog.csdn.net/weixin_46959681/article/details/111768618



前言

在上一篇博客中,笔者就指针的些许基础概念进行了讲解,但就指针的实现范其围远不止如此。指针可以与其他的概念进行组合,如配合数组实现指针数组、数组指针,配合函数实现指针函数、函数指针等。笔者就上述内容在接下来的篇幅中展开详细说明。


指针数组

指针数组实际上是关于指针的数组,核心为一个数组,该数组中的每一个元素都是指针,如 int *parray[N]

#include <stdio.h>
#define N 3

int main()
{
	int i;
    int array[N] = {5, 50, 500};
    
    //定义了一个指针数组。
    int *parray[N];
    
    //同指针声明的初始化一样,这里为指针数组的初始化。
    for(i = 0; i < N; i++)
    	parray[i] = &array[i];

    //使用for遍历指针数组。
    for(i = 0; i < N; i++)
    	printf("%d\n", *parray[i]);

    return 0;
}

运行结果:

5
50
500

数组指针

数组指针实际上关于数组的指针,核心为一个指针,该指针指向某个数据类型的数组,如 int (*p)[3]

注意⚠️:数组的指针在偏移的时候偏移的是整个数组的大小。

#include <stdio.h>
#define N 3

int main(){
	int array[N] = {1, 10, 100};
	int i = 0;
	
	// (*p)代表了数组整体。 
	int (*p)[N];
	//取地址一维数组名才是数组指针,第一个元素地址&array[0]不行。
	p = &array; 

	//打印数组指针中所有整型元素的地址。
	for(i = 0; i < N; i++){
		printf("%p\n", p[i]);
	}
	//打印数组指针中所有整型元素的值
	for(i = 0; i < N; i++){
		printf("%d\n", (*p)[i]);
	}
	return 0;
}

运行结果:

0x7ffea03116cc
0x7ffea03116d8
0x7ffea03116e4
1
10
100

对于数组、指针两者组合何以成为数组指针、指针数组,在符号 *[]() 三者的优先级以及使用画图阐述,这篇博客讲解的很到位,建议阅读。链接


指针函数

指针函数实际上是一个返回指针的函数。 其核心是一个函数,该函数的返回值是一个指针,如 void *add(int x, int y),返回值是一个无类型的指针,是一个地址。

#include <stdio.h>

void *add(int a, int b)
{
        int sum;
        sum = a + b;
        //定义一个整数型指针,将其作为一个返回值。
        int *p;
        p = &sum;
        return p;
}

int main()
{
        int x,y;
        printf("Please enter two numbers:\n");
        scanf("%d%d", &x,&y);

        //调用指针函数。
        int *p = add(x, y);
        printf("sum = %d\n",*p);
        return 0;
}

运算结果:
指针函数


函数指针

与指针函数不同,函数指针的本质是一个指针变量。该指针的地址指向了一个函数,所以它是指向函数的指针。(换句话说,该指针变量存放的是函数的地址。)

#include <stdio.h>
void printwelcome()
{
        printf("hello, world.\n");
}

int Add(int a, int b)
{
        return a+b;
}

int main()
{
//      定义一个函数指针。
        int (*padd)(int a, int b);
//      两种初始化皆可。
        padd = Add;
//      padd = &Add;

		int ret1;
//      函数调用三者皆可以,建议使用第一种。
        ret1 = fun1(1, 2);
//      ret1 = (*Add)(1, 2);
//      ret1 = (*fun1)(1, 2); 
		printf("ret1 = %d\n", ret1);

        void(*p)();
//      给函数指针赋值,千万不要写 printWelcome(),函数名如同数组名皆为地址。
        p = printwelcome; 

//      函数调用三者都可以,建议使用第一种。
        p();
//      (*p)();
//      (printwelcome)();
        return 0;
}

运行结果:
在这里插入图片描述


浅说malloc

函数 malloc 的全称是 Memory allocation(动态内存分配),原型为 void *malloc(size_t.size) 。用于申请一块连续的指定大小的内存块区域以 void* 类型返回分配的内存区域地址。当无法知道内存具体位置的时候,想要绑定真正的内存空间,就需要用到动态的分配内存,且分配的大小就是程序要求的大小。无类型的指针也是一个指针变量,但是我们不知道它指向的空间是什么属性。在C与C++中规定,void* 类型可以通过类型转换成任何类型的指针,如 int *p = (int *)malloc(10*sizeof(int)+1)

演示代码: malloc.c

/* malloc.c */
#include <stdio.h>
#include <stdlib.h>

int main()
{
        int n;
        printf("请输入学生总人数:\n");
        scanf("%d", &n);

        //将开辟的无类型的指针强转成整形类的指针。
        int *parray;
        parray = (int *)malloc(n * sizeof(int) + 4);

        int i;
        for(i = 0; i < n; i++)
        {
                printf("请输入第%d个学生的成绩: \n", i+1);
                scanf("%d", &parray[i]);
        }

        for(i = 0; i < n; i++)
        {
                printf("第%d个学生的成绩是%d \n", i+1, parray[i]);
        }
        return 0;
}

运行结果:

malloc.c

计算机内存泄漏的典型表现:程序刚开始运行时非常流畅,但在运行几个小时或者几天后程序就崩溃了。 原因可能有以下几点:

  1. malloc申请的内存空间,程序不会主动释放一直处于占据状态(Linux会主动释放 );
  2. 程序在 for 循环中不断的申请开辟内存空间;
  3. 程序访问已经被释放掉的内存空间;
  4. 程序访问没有权限的内存。

类型总结

    
    int i; //定义一个整型变量
    int *p; //定义了一个指向整型变量的指针
    int array[5] = {0}; //定义了一个含有5个整型元素的数组
    
    int *parray[4]; //定义了一个指针数组,含有4个指向整型数据的指针元素(地址)。
    int a = 1;
    int b = 2;
    int c = 3;
    int d = 4;
    //初始化指针数组
    parray[0] = &a; 
    parray[1] = &b;
    parray[2] = &c;
    parray[3] = &d;
    
    //数组指针
    int array[4] = {0};
    //又或者是 int[4](*p),但前者比较美观。 (指针数组) 
    int (*p)[4];  
    p = array;    
    
    int f(); //f为返回值为整型数值的函数
    int* p(); //p为一个返回值为指针的函数,该指针指向整型数据。
    int (*p)(); //p为指向函数的指针,该函数返回一个整型值。
    void *p(); // p是一个指针变量,基本类型为void(空类型),不指向具体的对象。
}

volatile

volatile 是类型修饰符(函数)。volatile 的作用是不再寄存器中取数据,而是从内存中获取数据,避免了多线程中可能出现的“数据更新错误的情况”。虽然牺牲了效率,但是提高了程序运行结果的准确度。

变量名前若添加了类型修饰符,则说明运算中会从内存中重新装载内容,而不是直接从寄存器中拷贝数据。典型场景有并行设备的硬件寄存器(如:状态寄存器),一个中断服务子程序中访问到非自动变量,多线程应用中被几个任务共享的变量等等。

同时,volatile 和编译器的优化有关。在某次线程内,当读取一个变量时,为了提高读取速度,编译器进行优化时有时会先把变量读取到一个寄存器中。当再次读取变量值时,就从寄存器中直接读取。当变量值在本线程里改变时,会同时把变量的新数值拷贝到寄存器中,以保持一致。当变量因别的线程值发生,寄存器中的值不会产生相应改变,从而造成相应读取的值和实际的变量值不一致。当寄存器因别的线程值发生改变,同理有原变量的值也不会相应改变,也会造成应用程序读取的值和实际的变量值不一致。


文章更新记录

  • 文章脉络初步搭好。 「2020.1.1 21:06」
  • 文章后半段内容整理。 「2020.1.2 10:17」
  • 两次修改“指针函数”一节的代码。 「2021.4.24 14:05」
  • 对于“malloc”一节做出文字和代码上的修改以及拓展。 「2021.4.24 15:52」

P.S.1 以后或许把 malloc 和 volatile 两节 作为单篇博文。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值