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

文章转载请注明出处,加上原文链接,谢谢!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

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


指针函数

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

#include <stdio.h>
int *add(int x, int y)
{
    
    
        int sum;
        sum = x + y;
        //定义一个指针,将其作为一个返回值。
        int *p;
        p = &sum;
        return p;
}

int main()
{
    
    
        int x;
        int y;
        printf("Please enter two numbers:\n");
        scanf("%d", &x);
        scanf("%d", &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 (*fun1)(int a, int b);
        //两种初始化皆可。
        fun1 = Add;
//      fun1 = &Add;

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

        void(*p)();
        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)

内存泄漏有以下原因:

  1. malloc申请的内存空间,程序不会主动释放。(Linux会主动释放 )
  2. 也许程序在循环中一直申请内存空间。
  3. 程序访问已经被释放掉的内存空间
  4. 访问没有权限的内存
#include <stdio.h>
#include <stdlib.h>

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

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

        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;
}


类型总结

    
    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」

猜你喜欢

转载自blog.csdn.net/weixin_46959681/article/details/111768618