声明:本文为个人所用,初心是用来查漏补缺,不是体系化全面介绍,并不适合0基础。
C的基础篇
1. 指针基础
1.1从不同角度理解指针
(1)从体系结构(入门)的角度来讲,指针即计算机存储空间(内存、寄存器、支持虚拟内存用于虚拟内存的外存)的某一个单位大小(一般为1B大小)的地址(编号)。
(2)从组成原理角度,指针是访问内存的存储空间时内存的多路选择器进行内存空间选择的源信号。
1.2 指针变量指明的内容
(1)指针变量本身存储的数值是指针指向的目标数据对象存储的首地址
(2)指针类型标记数据对象空间大小
例如:
#include <stdlib.h>
int main()
{
int i = 100;
int *p_i = &i;
char c = 'w';
char *p_c = &w;
p_c = p_i;
//这里编译器会报错,虽然二者都是一个8B大小(64位机中)的无符号整型数据,但是二者指针类型不同,而指针通过指针类型标记指向的数据占用了多少字节,以此来计算数据起始地址(始地址:指针存储的首地址 ~ 末地址:指针存储的首地址+占用字节数)以此来准确访问数据在访问内存的一块区域。
}
1.3指针访问数据在内存中的原理
正如上个例子中说的:
指针通过(1)存储的数值,即首地址标记指向的数据在内存中占据的内存首地址(2)指针的类型标记指向的数据占用了多少字节内存空间——以首地址和占据大小来计算数据起始地址(始地址:指针存储的首地址 ~ 末地址:指针存储的首地址+占用字节数),以此来准确访问数据在访问内存的一块区域。
1.4 指针类型占用的空间大小
所有类型的指针变量本身所占的内存空间大小都为机器字节长
问:为什么是所有指针变量,刚才不是还说指针类型不同,数据占据字节大小不同,所以不能相互转换吗?
答:指针类型不同,是指针指向的也即指针变量存储的数值,这个数值表明的变量的首地址,这个变量也被称为目标变量,指针的类型是目标变量的类型,而目标变量占据的内存空间大小可能相同也可可能不同,所以不能相互转换,但是指针本身是存储的目标变量的首地址,而这个首地址的范围是无法超出机器的寻址范围的(例如32位机器为2^32寻址范围,也即可用32为2进制数表示也即4B;同理64位机器寻址范围为8B,所有类型指针的大小也为8B),因此指针的大小一般为机器的寻址空间大小。
1.5指针可以使用强制类型转换
通过使用强制类型转换可以完成指针类型转换从而达到不同类型指针相互赋值的目的,但是你仅仅是完成了指针首地址赋值,而这个强制类型转变后的指针已经不再是你之前的指针了(就像你初恋已经不再是你之前的初恋了),指针的类型已经发生了改变,当你对强制类型转换后的指针操作的时候你一定要时刻提醒自己,强制类型转换之后的指针现在的类型不是之前的类型了,访问内存的时候取出的内存大小不一定是之前的连续大小的内存空间了,也即最最最容易被人忽视的一点——指针通过类型标记每次用指针操作内存时连续访问的内存空间大小。
SUM:指针变量(1)通过存储的值标记指向的变量在内存中的首地址(2)通过指针变量的类型标记指针指向的变量在内存中占据的连续的存储空间大小。
2.指针运算
既然指针内存储的是整数,那他能否进行运算?运算的规则(法则)是什么?有什么含义呢?
2.1运算规则
(1)指针的整型赋值运算 —— 一般情况下指针变量不能被常量赋值;
#include<stdio.h>
int main()
{
char *p_c;
short *p_s;
int *p_n;
p_c = 100;
p_s = 100;
p_n = 100;//指针变量无法通过整型数值直接赋值
p_c = (char*)100;
p_s = (short*)100;
p_n = (int*)100;//指针变量可以通过强制类型转换后的整型常量赋值(其实此时已经不再是整型常量了)
}
当进行强制类型转换之后,编译器对数值100的解释已经从数值变为对应指针类型的内存首地址了
(2)指针加减数值运算——指针变量类型的另一个作用:指针变量加减的为几个步长
对指针变量进行加减运算时,比如:Elemtype *p_elem = (Elemtype *)100; p_elem = p_elem + k;
这个式子运算结果为100 + k *( sizeof(Elemtype) ),该结果的含义是:指针p_elem指向的内存首地址为100 + k *( sizeof(Elemtype) ) ,指向的范围为 sizeof(Elemtype) 。其中我们称指针类型的大小为 步长,指针每次加减数值运算的大小为(数值*步长)。
(3)同类型指针之间相减——获得首地址相距的步长的大小。
(4)使用指针盲目进行直接通过 +- 数值修改地址来访问数据的骚操作时一定要注意:变量在内存中的存放不一定是首尾相连的,ps.数组是个例外(这涉及到变量的内存对齐,这样计算机就可以通过半字长、字长寻址来直接一个变量的首地址,而不是访问到了变量存储的中间地址,从而提高访存的效率)。
(5)没有意义的(C中无法编译)指针相关运算:
-指针类型与整型进行乘除运算
-同类型的指针相加
-同类型的指针乘除
3.指针与数组.使用指针访问数组
3.1 使用指针+步长访问数组元素
int main()
{
int arr[5]= {1,2,3,4,5};
int *p = &arr[0];
for(int i =0;i<5;i++)
{
printf("%d",*(p+i));//打印: (1)首地址为(p+i*步长). (2) 大小为p指向的变量类型int(4B)的内存空间中的数据.
}
}
★3.2 直接通过数组名获取首地址
(1)数组名的类型并不是数组元素类型的指针,但是数组名在表达式中会默认转换为数组首元素的地址,因此我们在表达式中可以直接将数组名默认作为数组元素的首地址(但是编译器在背后悄悄给你进行了一些工作)
(2)当你对数组名使用sizeof的时候,数组名的真实样貌就暴漏了,对数组名sizeof得到的结果为整个数组的大小,而不是首元素的大小;
★3.3访问数组元素的两种方法
(1)数组名[下标]
(2)*(数组名+偏移量)
问:为什么有上述两种方法?
答:上述两种形式完全等价,本质上下标运算符[ ] 最终会展开为指针的形式 ——(1)的运算方法编译器会默默地为你进行转换为(2)的形式,例如:A[i]其实编译器看到这个就会进行A[i] = *(A+i)
C的进阶篇
4.多级指针与指针数组
前言:我们在DS中树、图的邻接矩阵存储中会用到指针数组,虽然可以用C++提供的引用机制在很多场合来避开二级指针的使用,但学习二级指针对于对指针在程序中的特性的理解可以更加深入,可以说搞明白了二级指针才能理解”指针是C的精髓“这句话,所以二级指针应当予以重视。
4.1多级指针——存储指针地址的指针变量
★4.2指针数组——(元素类型为)指针(的)数组
(1)声明: 数组元素类型 数组名[元素个数]; 例如:int * p_arr[4];//声明了一个数组大小为4,元素类型为int*的数组。(ps.你可以利用数组指针干嘛?超乎你的想象,最起码在DS里的高级数据结构——树、图的存储就已经能够很好的体现了)。
★★(2)应用——矩阵存储的实现
int** InitialSymmerticMaterix(int r,int c)
{
int **matrix_p = new int*[c]; // (matrix_p)->|int*|int*|int*|int*|
int **create_p = matrix_p;//(create_p)->|int*|int*|int*|int*|
for(int i = 0;i<c;i++)
{
*(create_p+i) = new int[r];
}
return matrix_p;
}
//需要注意的是,在使用完这个二维数组后,需要手动释放内存,避免内存泄漏,即使用 delete[]
释放分配的内存。
void DeleteMatrix(int** &matrix)
{
for (int i = 0; i < c; i++)
{
delete[] arr[i];
}
delete[] arr;
}
void modifyMatrix(int** &matrix, int rows, int columns)
{
// 对二级指针指matrix引用以此到达再函数中对其内容进行修改
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
matrix[i][j] = i * j;
}
}
}
int mian()
{
int** arr = InitialSymmerticMaterix(r, c);
}