目录
什么是指针?
指针的大小:
*的含义 :
解引用:
指针对数组的操作:
“[]”也具有解引用的功能
* (p + 1) <=> ar[2] 也就是 p + 1 <=> &ar[2]
作业练习:
作业一:
作业二:
利用数组:
利用指针:
作业三:
今天对指针进行了系统的学习,回顾一下今天所讲的知识点:
首先需要明确的是:指针,就是地址的别名,用来存放一个值的地址。
指针的大小与指针的级别无关,也与指针的类型无关。
例如我在这里分别设置int类型指针和double类型指针,我们分别来看他们的大小:
如图,int类型的指针和double类型的指针的大小都是8字节,所以指针的大小与指针的类型并没有关系。
例如我在这里设置一级指针p和二级指针q,我们分别来看他们的大小:
如图,一级指针和二级指针的大小都是8字节,所以指针的大小与指针的级数无关。
指针的大小只和操作系统(OS)的种类有关,在x86架构(32bit)下,指针的大小就是4字节,在x64架构(64bit)下,指针的大小就是8字节。例如我的苹果电脑是64位操作系统,指针的大小就是8字节:
*的在C语言编程中有三种含义:
第一种:乘法的运算符号,表示方法为:数据*数据
第二种:解引用:*变量
第三种:指针的定义:类型 *名称
在这里我查阅了相关资料,更加深入了解了解引用,“*” 属于指针的操作符,它的作用就是引用指针指向的变量值,前面说到过,指针的别名就是地址,也就是指针存放变量的地址,然后指针指向变量,所以*的作用就是引用指针所指向的变量值,引用其实就是引用该变量的地址,然后再利用指针操作符“*”将该地址对应的东西解开,所以就是解引用。
例如我这里写的代码:
整形变量a的值是10毋庸置疑,这里设置两个指针分别是p和q,其中指针p存放了变量a的地址,指向a,二级指针q存放了一级指针p的地址,而p因为存放了a的地址又指向a,所以二级指针q指向p最终指向a,所以解引用*p就是a本身,解引用**q也是a本身。
如图分别将a,*p,**p以十进制输出,得到的结果均为10,上面论述有据可依。
我在这里给出一个元素个数为5的一维数组,通过指针随意调换数组中的两个数值。
例如我需要更换数组下标为0和数组下标为5的数,对应数组里面的第一个数和第五个数:
#include
#include
void Swap(int *ar,int index1,int index2)
{
assert(ar != NULL && index1 >= 0 && index2 >=0);
int temp = ar[index1];
ar[index1] = ar[index2];
ar[index2] = temp;
}
int main()
{
int ar[5] = {1,2,3,4,5};
int len = sizeof(ar) / sizeof(ar[0]);
Swap(ar,0,4);
for(int i = 0;i < len;i++){
printf("%d ",ar[i]);
}
return 0;
}
如图为运行结果,调换完成:
这里我写了一个Swap函数,其中形参index1和index2对应的就是需要交换的元素的下标,通过指针指向数组的下标实现元素的调换。
对此程序进行分析:
main主程序中有ar(地址为0x1),在main主程序中调用Swap(ar,0,4),在Swap函数中,有指针int *占四字节,ar保存下方实参和形参的关系,属地址拷贝。*ar与主函数中的ar数组进行关联(地址拷贝),在程序中temp保存index1位置元素,通过swap中的ar指针访问到数组的index1号下标元素,此时0号下标对应的元素为1,赋值给temo,然后index2号下标元素,赋值给ar指向的index2号下标元素,然后把temp赋值给index1号下标。
在此程序中,解引用通过数组的下标来进行元素的访问,比如程序中的语句:ar[index1] = ar[index2];*ar为解引用,ar[0]也具有解引用的功能,这两者其实是有关联的:
*ar <=> *(ar + 0);
ar[2] <=> *(ar+2);
这类似于我的上一篇博客,二维数组的学习 中所提到的数组名加1,我在计算二维数组的行数的时候曾经说过,行数 = 数组的总大小 / 数组类型,而当我以十进制输出数组和数组+1时发现两个地址所产生的差值 / 数组中元素的类型名的大小时,得出来的结果正好是二维数组一行的个数:
所以类型对指针变量起到两个作用:
(1)解析存储单元的大小
例如,int a = 10; int *p = &a; 因为指针类型为int 所以存储单元的大小为4字节
(2) 指针变量加1的能力
因为在这里牵扯到计算机的存储方式问题,因为我是macOS,不同于Windows操作系统,已知Windows电脑的存储方式是小端存储,即最低地址存放最低字节,先来验证我的电脑是大端存储还是小端存储:
#include
void judge_litteEndian()
{
int i = 48;
int *p = &i;
char c = 0;
c = *((char *)p);
if(c == '0'){
printf("你的计算机的存储方式为小端存储\n");
}
else{
printf("你的计算机的存储方式为大端存储\n");
}
}
int main()
{
judge_litteEndian();
return 0;
}
将int 48存起来,然后取得其地址,再将这个地址转为char* 这时候,如果是小端存储,那么char*指针就指向48;
48对应的ASCII码为字符‘0’
运行结果为:
我的操作系统的存储方式为小端存储。
在这里给出一个一位数组:
int ar[5] = {1,2,3,4,5};
按照小端存储的方式进行存储,数组元素1和2的地址分别为:0x00000001、0x00000002,如图填充地址,当定义指针p指向数组时,(p + 1)则代表的是指针向后偏移一个单元格,此时指向20,所以*(p + 1)就是引用(p + 1)指向元素的地址,然后解开对应地址的元素,也就是数组中的第二个元素,2
所以在这里:
编一个程序,输入月份号,输出该月的英文月名,例如,输入3,则输出“March”.要求用指针数组处理:
#include
int main()
{
int m = 0;
printf("Please input a number:\n");
scanf("%d",&m);
char *month[13] = {"Illegal_month","Januray","Feburay","March","April","May","June","July","August","Septmber","October","November","December"};
if(m <= 12 && m>= 0){
printf("The month is %s",*(month + m));
}
else{
printf("The month is %s",*(month));
}
return 0;
}
我分别输出0和12,运行结果为:
输出完成
有n个整数,使前面各数顺序向后移m个位置,最后m个数变成最前面m个数,见图。写一个函数实现以上功能,在主函数中输入n个整数和输出调整后的n个数:
#include
#include
#include
int array_backwards(int *array,int n,int m)
{
assert(array != NULL && n > 0 && m >0);
int i = 0;
int j = 0;
int temp = 0;
for(i = 0;i < m;++i){
temp = array[n - 1];
for(j = n - 2;j >= 0;j--){
array[j + 1] = array[j];
}
array[0] = temp;
}
return 0;
}
int main()
{
int m = 0;
int n = 0;
printf("请输入数组元素的个数:\n");
scanf("%d",&n);
int array[n];
printf("请输入这些元素:\n");
for(int i = 0;i < n;++i){
scanf("%d",&array[i]);
}
printf("请输入您需要移动的元素个数:\n");
scanf("%d",&m);
array_backwards(array,n,m);
for(int i = 0;i < n;++i){
printf("%d ",array[i]);
}
return 0;
}
运行结果:
这个方法的优点就是不用开辟额外的空间,直接利用指针在原数组进行操作,时间和空间复杂度都较低,先来理解这个方法,
例如在这里我给出一个数组:
int ar[5] = {1,2,3,4,5};
这里我需要将前两位挪到后面,后面的三个数挪到最前面,最终结果为:3 4 5 1 2
这里分三步进行:
第一步:将原数组整个进行逆转:变为:5 4 3 2 1
第二步:对前三个元素进行操作,使其变成3 4 5,那么我只需要将0号下标元素和2号下标元素进行调换即可,此为局部逆转
第三步:对后两个元素进行操作,使其变成1 2,也就是将3号下标元素,与末尾元素进行调换
由于所有的逆转都要进行调换工作,在这里直接写一个利用指针进行调换的函数Swap
上述三步操作如何写成适用于所有数组的函数?
全局逆转可以利用调换函数Swap,从第一个元素和最后一个元素调转开始,依此类推,逆转的终止条件就是左右指针重合或者相邻。
局部逆转分为两段,第一段可以从0号下标元素和len - m - 1号下标元素逆转(m代表需要挪动的数据个数),循环;
第二段从len - m号下标元素到末尾元素(len - 1号下标元素)调换,循环。
将思路代码化:
void Swap(int *p,int *q)
{
assert(p != nullptr && q != nullptr);
int temp = *p;
*p = *q;
*q = temp;
}
void Reverse(int *ar,int begin_index,int end_index)
{
assert(ar != nullptr && begin_index >= 0 && end_index >= 0);
int *p = ar + begin_index;
int *q = ar + end_index;
assert(p != nullptr && q != nullptr);
while(p < q){//指针p和q的不重合和不相邻是循环的条件,反之则终止
Swap(p,q);
p++;
q--;//每次循环后p指针向后偏移一位,q指针向前偏移一位
}
}
void Adjust(int *ar,int len,int m)//调整函数
{
assert(ar != nullptr && len >= 0 && m >= 0);
Reverse(ar,0,len - 1);//对数组中的元素进行全部逆转
Reverse(ar,0,len - m - 1);//区间逆转
Reverse(ar,len -m,len - 1);//区间逆转
}
int main()
{
int ar[5] = {1,2,3,4,5};
int m = 3;
Adjust(ar,5,3);
for(int i = 0;i < 5;i++){
printf("%2d",ar[i]);
}
return 0;
}
运行结果:
调换完成
写一函数,实现两个字符串的比较。 即自己写一个strcmp函数,函数原型为
mtstrcmp(char ·*pl.char ·><' p2);
设 pl 指向字符串 sl, p2 指向字符串 sZ。 要 求 当 sl=s2 时,返回值为 O;若 sl-::/=-s2.返回它 们二者第 1个不同字符的 ASCII码差值(如"BOY"与"BAD",第 2个字母不同,0与 A之差为79-65=14)。 如果sl>s2,则输出正值;如果sl 例如我分别输入字符串:BOY和BAD,运行结果如图:#include