菜鸟A给另一菜鸟B一个单纯的内存地址,让他去内存基地获得需要的信息,菜鸟B兴冲冲地去了,一到却傻眼了,看到一排开关,线性排列,满腹疑云。应该取多少个字节?是数据类型还是代码类型?提取后应该按什么编码方案解释?菜鸟B只得打电话问老鸟C,老鸟告诉他,一个单纯的地址不行,要有类型,类型提供了字节长度信息、编码方案信息,让A给你一个指针吧,指针是具有类型信息的地址,不是一个单纯的内存地址。
1 计算机的内存可随机访问,可寻址;
2 程序运行前的加载阶段,要将代码和代码中定义的全局、局部静态数据加载内存;
3 程序运行后,操作系统会为每个运行的程序(进程)提供一定的栈空间和堆空间;
4 数据类型决定了内存空间的大小和编码、解码方式(如整型的补码、IEEE754浮点编码方案、ASCII字符编码方案等);
5 不同的CPU有不同的代码集(CPU的抽象);
6 函数是编程语言程序编写的基本模块抽象,函数名是是代码块的命名,可以赋值给一个函数指针,函数指针的类型由返回值类型,参数个数和类型指示;
7 指针是一类特殊的内存地址,特殊性在于其有类型,数据类型,函数类型或类型暂定的void类型。所以指针可以区分为三类:
数据指针;
函数指针;
void指针;
严格意义上的指针并不是指针变量,指针变量是一个左值,而指针特值可以赋值给一个指针变量的右值。通常,在上下文中,指针变量简称为指针。
所以,地址、指针、指针变量并不是相同概念,而是相关概念。
7.1 数据指针,不同的数据类型具有不同的内存长度,指针指向不同的数组元素或栈空间的不同部分时,称为指针的移动,移动可以由指针的算术运算完成,如有指针p、q指向某一数据,整数n,通常的算术运算有:
① p++、p--、++p、--p;
② p+=n、p-=n;
③ n = p-q;
④ bool flag = p>q;
第①、②类运算实现指针的移动,移动是为了指向某一数据元素的首地址,取出其类型所指引的字节数,按其类型所指定的编码、解码方式解析数据,按其类型所指定的操作对数据进行操作。虽然内存按字节编址,但这里按单纯的字节数移动没有意义,需要以类型规定的字节数为步长才有意义,步长的计算有编译器隐式完成:
① p++、p--、++p、--p; // 步长为sizeof(*p)、前后移动一个步长,指向前、后数据元素
② p+=n、p-=n; // 步长为sizeof(*p),整体偏移sizoef(*p)*n个字节,指向第n个元素
③ n = p-q; // 两个指针间的元素个数(而不是字节数,当然,字符的元素个数与字节数是一致的);
④ bool flag = p>q; // 比较两个地址的前后关系,如实现memmove()函数时
CPU的寄存器IP也可以实现移动到下一条指令或跳转到某一条指令(某一个地址)的操作。
对于链式存储,可以由具体的指向自身的指针赋值实现移动:
typedef struct ListNode{
int data;
struct ListNode *next;
}ListNode,*ListPtr;
void printList(ListPtr head)
{
ListPtr curr = head;
while(curr)
{
printf("%d ",curr->data);
curr = curr->next; // 自指自指针赋值实现移动
}
}
7.2 函数指针,函数也是有类型的,函数的返回值、函数的参数类型和数量整体决定了函数指针的类型。
#include <stdio.h>
void demo()
{
printf("hi!\n");
}
int main()
{
demo();
void (*funcPtr)() = demo;
funcPtr();
(*funcPtr)();
getchar();
return 0;
}
7.3 void指针,称为泛型(generic)指针,一种数据类型暂定的指针。当获取某一内存地址,但类型暂定时,可以用void指针来声明和定义。当然,解引用时需要类型确定,需要做强制类型转换,转换为具体的操作类型;
void print(void *p, int t){
if (t == 1)
printf("%d\n", *(int *) p);
else if (t == 2)
printf("%0.3lf\n", *(double *) p);
else
printf("error: unknown type\n");
}
8 指针类型转换可以实现强制类型类型解释字符编码
bool endian()
{
int d = 0x01020304;
char* p = (char*)&d;
if(*p == 1)// 判断高位,32或64位系统,高位的字节都是统一的
{
printf("首地址(低址)匹配高位,是大端!\n");
return false;
}
else
{
printf("首地址(低址)匹配低位,是小端!\n");
return true;
}
}
9 指针的己型、己址、己值、他型、他址、他址
10 指针类型与目标类型
看以下动态内存声明:
int (*darrp)[COL] = (int(*)[COL])malloc(sizeof(int)*row*col);
darrp指针的指针类型是:int(*)[COL];
darrp指针的指针类型是:int[COL];
demo:
#include <stdio.h>
#include <stdlib.h>
const int ROW = 3;
const int COL = 4;
int arr[ROW][COL] = {1,2,3,4,5,6,7,8,9,10,11,12};// 静态二维数组
void input(int(*arrp)[COL])
{
for(int i=0;i<ROW;i++)
for(int j=0;j<COL;j++)
arrp[i][j] =arr[i][j];
}
void input2(int**pp,int r,int c)
{
for(int i=0;i<r;i++)
for(int j=0;j<c;j++)
pp[i][j] =arr[i][j];
}
void output(int(*arrp)[COL])
{
for(int i=0;i<ROW;i++)
for(int j=0;j<COL;j++)
printf("%d ",arrp[i][j]);
printf("\n");
}
void output2(int **pp,int r,int c)
{
for(int i=0;i<r;i++)
for(int j=0;j<c;j++)
printf("%d ",pp[i][j]);
printf("\n");
}
void demo(){
int i,j;
output(arr);
int * parr[COL];// 静态指针数组(行数是常量)
for(i=0;i<ROW;i++)
parr[i] = arr[i];
output2(parr,ROW,COL);
int (*arrp)[COL] = arr;// 静态指针数组(列数是常量)
output(arrp);
int row = 3;
int col = 4;
int (*darrp)[COL] = (int(*)[COL])malloc(sizeof(int)*row*col);// 动态态指针数组(列数是常量)
input(darrp);
output(darrp);
free(darrp);
int **darr =(int**)malloc(sizeof(int*)*row); // 动态指针数组
for(i=0;i<row;i++)
darr[i] = (int*)malloc(sizeof(int)*col);
input2(darr,row,col);
output2(darr,row,col);
for(i=0;i<row;i++)
free(darr[i]);
free(darr);
}
int main()
{
demo();
getchar();
return 0;
}
/*
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4 5 6 7 8 9 10 11 12
*/
11 指针适用场合
ref:
-End-