原创首发于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 = ∑
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申请的内存空间,程序不会主动释放一直处于占据状态(Linux会主动释放 );
- 程序在
for
循环中不断的申请开辟内存空间; - 程序访问已经被释放掉的内存空间;
- 程序访问没有权限的内存。
类型总结
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 两节 作为单篇博文。