在过去学习的C语言的一年里对于C语言里面许多的细节都是一知半解,甚至有时在写一些简单的逻辑关系时还需要对某个基础知识点进行翻书查阅。编程语言的学习贵在动手和解决BUG,为了记录这学习的过程,本着督促自己学习态度和进度的目的。我打算在CSDN上记录我学习的点点滴滴。同样记录的过程起到了良好的复习作用以及加深印象。趁着这次漫长的寒假同样也是最后的一个寒假,开启了我的2020第一个计划。武汉加油!中国加油!
指针和数组
基本概念
指针:提供一种以符号形式可以使用地址的方法。
那为什么使用指针的程序更加有效率?
答:因为计算机的硬件指令非常依赖于地址,在一定意义上指针可以将程序员想要传递给计算机的指令以更加接近机器的方式表达。
数组名是数组元素的首地址。也就是说mardles 和&mardles[0]是等价的`
int mardles[SIZE] = {20,10,5,39,4,16,19,26,31,20};
long answer;
int *apple;
apple = mardles;
apple = &mardles[0];
answer = sum(mardles,SIZE);
对于数组指针来说,在数组指针加上一个数时,它的值会发生什么变化?
#include <stdio.h>
#define SIZE 10 //数组长度
int main(void)
{
int mardles[SIZE] = {20,10,5,39,4,16,19,26,31,20};
int *apple; //用于存放数组的首地址
apple = mardles;
for(int i = 0;i<SIZE;i++)
{
printf("mardles[i] = %d, apple = %p, mardles = %p\n",mardles[i],apple+i,mardles+i);
}
return 0;
}
运行结果:
mardles[i] = 20, apple = 0x7fffffffdcf0, mardles = 0x7fffffffdcf0
mardles[i] = 10, apple = 0x7fffffffdcf4, mardles = 0x7fffffffdcf4
mardles[i] = 5, apple = 0x7fffffffdcf8, mardles = 0x7fffffffdcf8
mardles[i] = 39, apple = 0x7fffffffdcfc, mardles = 0x7fffffffdcfc
mardles[i] = 4, apple = 0x7fffffffdd00, mardles = 0x7fffffffdd00
mardles[i] = 16, apple = 0x7fffffffdd04, mardles = 0x7fffffffdd04
mardles[i] = 19, apple = 0x7fffffffdd08, mardles = 0x7fffffffdd08
mardles[i] = 26, apple = 0x7fffffffdd0c, mardles = 0x7fffffffdd0c
mardles[i] = 31, apple = 0x7fffffffdd10, mardles = 0x7fffffffdd10
mardles[i] = 20, apple = 0x7fffffffdd14, mardles = 0x7fffffffdd14
可见在数组指针后加上一个数会导致,数组指针指向了下一个元素的地址,而不是下一个字节的地址。同样这也是数组指针需要声明类型的原因。在指针前面使用*运算符可以获取数组指针指向的对象的值。
mardles = apple;
*(apple +1) 等价于 mardles[1]
apple+1 等价于&apple[1]
这里需要注意 apple + 1和(apple +1)不是一个概念
*apple +1的意思是:由于 运算符的优先级高于+号,系统会优先执行apple 取到了值后加一。
*(apple+1)的意思为在apple地址后移一个元素。
printf("mardles[1] = %d ,mardles 的地址为 = %p\n",*(apple+1),apple+1);
printf("mardles[1] = %d ,mardles 的地址为 = %p\n",mardles[1],&apple[1]);
输出结果:
mardles[1] = 10 ,mardles 的地址为 = 0x7fffffffdcf4
mardles[1] = 10 ,mardles 的地址为 = 0x7fffffffdcf4
函数,数组和指针
指针形参
如果一个函数需要处理数组,则需要知道数组何时开始,何时结束。所以编写函数的需要传递的数组的首地址和数组个数,下面我们看一个实例。
//函数功能:调用子函数求和
#include <stdio.h>
#define SIZE 5 //数组长度
int sum (int *start ,int *end);//求和子函数
int main (void)
{
int mardles[5] = {5,5,5,5,5};
int add = 0、
add = sum(mardles,mardles+SIZE);
printf("add = %d\n",add);
return 0;
}
int sum (int *start , int *end)
{
int total = 0;
while(start<end)
{
total = total + *start;
start++;
}
return total;
}
运行结果:add= 25;
下面是对 apple++ 和++apple 的讨论
以及++运算符和*运算符优先级相同,根据自右向左的结合部分。
*apple++ :apple++是先取apple地址里的内容,然后再递增一,指向后一个元素
*++apple:则是先将地址后移一个元素再取值。
#include <stdio.h>
int main (void)
{
int a [3] = {1,2,3};
int b [3] = {4,5,6};
int *apple;
int *banana;
int *orange;
apple = banana = a;
orange = b;
printf("*apple = %d *banana = %d *orange = %d\n",*apple ,*banana ,*orange);
printf("*apple = %d *banana = %d *orange = %d\n",a[0] ,a[0] ,b[0]);
printf("*apple++ = %d *++apple = %d *(orange)++ = %d\n",*apple++,*++apple,*(orange)++);
printf("apple = %d banana= %d orange = %d\n",*apple,*banana,*orange);
return 0;
}
运行结果:
*apple = 1 *banana = 1 *orange = 4
*apple = 1 *banana = 1 *orange = 4
*apple++ = 1 *++apple = 2 *(orange)++ = 4
apple = 2 banana= 2 orange = 5
指针操作
目前我了解的指针操作只有8种,下面举例说明这8中操作。
//程序功能:验证指针的8种操作。
#include <stdio.h>
int main (void)
{
//定义一个数组
int urn[5] = {100,200,300,400,500};
//定义三个指针变量
int *ptr1,*ptr2,*ptr3;
//指针的赋值操作
ptr1 = urn;
ptr2 = &urn[2];
printf("pointer value,dereferenced pointer,pointer address:\n");
//其中 *ptr1 称为解引用操作,何为解引用?引用指针指向的变量值。
//其中 &ptr1 称为取址操作,当然指针变量也是存放在内存当中,所以指针变量同样也有地址。
printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p\n",ptr1,*ptr1,&ptr1);
//指针与整数相加减
printf("ptr1 + 4 = %p, *(ptr1 + 4) = %d\n",ptr1 + 4,*(ptr1 + 4));
//如果超出了数组的范围会输出什么东西
printf("验证\nptr1 + 5 = %p, *(ptr1 + 5)= %d\n",ptr1 + 5,*(ptr1 + 5));
//递增 递减指针
ptr1++;
printf("ptr1 = %p,*(ptr1) = %d\n",ptr1,*ptr1);
++ptr2;
printf("ptr2 = %p,*(ptr2) = %d\n",ptr2,*ptr2);
// //指针相加 指针并不可以相加!!!!!
// ptr1 = &urn[0];
// ptr2 = &urn[1];
// printf("ptr1 + prt2 = %p *(ptr1 + ptr2) = %d\n",ptr1 + ptr2,*(ptr1 + ptr2));
//指针相减 高地址减低地址可以算出两个之间相差了多少。
ptr1 = &urn[0];
ptr2 = &urn[4];
int SZIE = sizeof(ptr1);
//这个地方我原来以为需要先用sizeof求出字节长度然后算出相差。
printf("(ptr1 - ptr2)/SZIE = %p\n",(ptr1 - ptr2)/SZIE);
printf("ptr2 - ptr1 = %d\n",ptr2 - ptr1);
return 0;
}
运行结果为:
pointer value,dereferenced pointer,pointer address:
ptr1 = 0x7fffffffdd00, *ptr1 = 100, &ptr1 = 0x7fffffffdcf0
ptr1 + 4 = 0x7fffffffdd10, *(ptr1 + 4) = 500
验证
ptr1 + 5 = 0x7fffffffdd14, *(ptr1 + 5)= 32767
ptr1 = 0x7fffffffdd04,*(ptr1) = 200
ptr2 = 0x7fffffffdd0c,*(ptr2) = 400
(ptr1 - ptr2)/SZIE = (nil)
ptr2 - ptr1 = 4
保护数组中的数据
当我们在编写一个处理数据的函数时,我们可以通过直接传递值或者传递指针。对于数组来说,如果直接传递值得话,则会面对一个问题:必须分配足够的空间来用于存放数组数据的副本,然后将所有修改过的数据重新传回数组。但是如果使用指针进行数据传递的时候,则效率会更加高。
#include <stdio.h>
//函数功能: 将数组中的元素加上一个值 add
//形参:int *a 用于存放数组首地址 int n 用于存放数组中有几个元素 int add 用于存放需要加多少
int add_to(int *a ,int n,int add);
int main (void)
{
int urn[5] = {10,20,30,40,50};
add_to(urn,5,10);
for(int i = 0;i<5;i++)
{
printf("urn[%d] = %d\n",i,urn[i]);
}
return 0;
}
int add_to(int *a,int n,int add)
{
for(int i = 0;i<n;i++)
{
a[i] = a[i] + add;
}
}
输出结果:
urn[0] = 20
urn[1] = 30
urn[2] = 40
urn[3] = 50
urn[4] = 60
对形式函数使用const
当然如果编写的函数是不需要修改数组内容的时候,我们可以在前面加上const 修饰
#include <stdio.h>
#define SIZE 5
void Show_array(const double ar[],int n);
void Mult_array(double ar[],int n, double mult);
int main (void)
{
double dip[SIZE] = {20.0,17.66,8.2,15.3,22.22};
Show_array(dip,SIZE);
Mult_array(dip,SIZE,2.5);
Show_array(dip,SIZE);
return 0;
}
void Show_array(const double ar[],int n)
{
int i;
for(i = 0;i<n;i++)
{
printf("%8.3f\n",ar[i]);
}
}
void Mult_array(double ar[],int n,double mult)
{
int i = 0;
for(i = 0;i<n;i++)
{
ar[i] = ar[i]*mult;
}
}
运行结果:
20.000
17.660
8.200
15.300
22.220
50.000
44.150
20.500
38.250
55.550
很显然我们看见了在Mult_array函数中我们最后没有使用return 的返回机制,这里我猜测应该是由于指针的机制,后续我找到答案再补充。
关于const的一些补充
- const可以创建const 数组
使用const创建的数组可以访问其值,但是禁止修改数组元素。 - const可以创建const 指针
同样使用const的创建的指针也是禁止修改值的,但是可以指向别处。 - 指向const的指针我们通常应用于函数形参中,表明了函数不会使用指针改变数据。
规则:将const创建的数组或者非const创建的数组赋值给指向const的指针是合法的。但是非指向const的指针是禁止获取到const创建数组的元素。
这里个人觉得说的有点凌乱,等我后期重新总结的时候,我再回来修改。
指针和多维数组
当我看书看到了这块的时候,花费了我许久的时间,我才明白了里面的门道,下面我以较为清楚的语言来说明一下,也为了加深自己的印象以及日后复习。
为了让我们更快的理解指针和多维数组之间的关系,我们首先需要明白多维数组在计算机中是这么存放的。这里我们拿二维数组举例,在空间上二维数组是平面分布的,但是在计算机的存储单元中它是连续的一块内存单元。所以我们可以把二维数组看作:由多个一维数组构成的二维数组。
假设二维数组:int apple[4][2] = {{2,4},{6,8},{1,3},{5,7}}
我们可以将其看为由四个一维数组(每个数组有两个元素组成)的二维数组
- apple 数组名为首元素的地址,但是在这里apple 代表了一个含有两个元素的一维数组。
而apple [0] 代表的意思为一个一维数组首元素的地址。也就是数组元素2的地址,但是由于这两个地址都是同一起点,所以apple和apple[0]的地址相同,但绝对不是相同的东西。 - apple 和apple[0] 加一的效果是什么?
apple :上述对apple的描述为:代表了一个含有两个元素的一维数组的地址,所以在apple上+1,则会导致地址变成下一行一维数组的首地址,也就是{6,8}的地址
apple[0]+1:会指向第0行的一维数组的下一个元素也就是4的地址。 - 解引用一个指针。
apple[0] 等于apple[0][0]的地址
所以*(apple[0])等于apple[0][0]的值
apple 代表了apple[0]的地址也就是{2.4}数组的地址
所以*apple 等于apple[0][0]的地址 但是为了获取到apple上的值需要再进行一次解引用
**apple 等于 apple[0][0]的值
#include <stdio.h>
int main (void)
{
int apple [4][2] = {{2,4},{6,8},{1,3},{5,7}};
int *ptr1 = NULL;
ptr1 = apple[0];
printf("apple = %p ,apple[0] = %p\n",apple ,apple[0]);
printf("apple = %p ,apple[0] = %p\n",apple + 1,apple [0]+1);
printf("*apple =%p ,*apple + 1 = %p\n",*apple,*(apple+1));
printf("**apple = %d\n",**apple);
for(int i = 0; i < 4; i++)
{
for(int j = 0;j < 2;j++)
{
printf("apple[%d][%d] = %d,apple[%d][%d] = %p\n",i,j,*(ptr1 + (i*2 + j)) ,i,j,(ptr1+(i*2 + j)));
}
}
return 0;
}
输出结果:
apple = 0x7fffffffdcf0 ,apple[0] = 0x7fffffffdcf0
apple = 0x7fffffffdcf8 ,apple[0] = 0x7fffffffdcf4
*apple =0x7fffffffdcf0 ,*apple + 1 = 0x7fffffffdcf8
**apple = 2
apple[0][0] = 2,apple[0][0] = 0x7fffffffdcf0
apple[0][1] = 4,apple[0][1] = 0x7fffffffdcf4
apple[1][0] = 6,apple[1][0] = 0x7fffffffdcf8
apple[1][1] = 8,apple[1][1] = 0x7fffffffdcfc
apple[2][0] = 1,apple[2][0] = 0x7fffffffdd00
apple[2][1] = 3,apple[2][1] = 0x7fffffffdd04
apple[3][0] = 5,apple[3][0] = 0x7fffffffdd08
apple[3][1] = 7,apple[3][1] = 0x7fffffffdd0c
如果上面还有疑问,个人建议把代码多调两次就差不多了。过程确实非常痛苦难受了。
关于如何引用二维数组的值有一个小公式:
int *ptr1 = NULL;
ptr1 = apple[0];
apple[i][j]的值 = *(ptr1 + (i*N)+j)
N为数组的列数
函数和多维数组
如何使用函数来完成对数组的操作呢?
#include <stdio.h>
#define ROWS 3 //行
#define CLOS 4
void sum_rows(int ptr[][CLOS],int rows);
void sum_cols(int ptr[][CLOS],int rows);
int main(void)
{
int apple[ROWS][CLOS] = {{2,4,6,8},
{3,5,7,9},
{12,10,8,6}};
sum_rows(apple,ROWS);
// sum_cols(apple,ROWS);
return 0;
}
void sum_rows(int ptr[][CLOS],int rows)
{
int r;
int c;
int tot;
int add = 0;
for(r = 0; r < rows;r++)
{
tot = 0;
for(c = 0;c < 4;c++)
tot = tot + ptr[r][c];
add = add + tot;
printf("ROW %d: sum = %d\n",r,tot);
}
printf("add = %d\n",add);
}
输出结果:
ROW 0: sum = 20
ROW 1: sum = 24
ROW 2: sum = 36
add = 80
到这里那第一篇博客就算写完了,后续我想到什么知识点或者新内容我再补充进去,上述东西错误应该非常多,但是我比较粗心大意,万一大家看见这篇文章,发现里面的错误请在评论区指出来,我们进行一下技术交流,相互进步!虽然现在写的内容比较浅薄,但这也是我目前学到总结的东西,后续我会经常回顾,温故而知新。以后水平肯定会越来越高的!有一说一,打写这个博客以来,我学习的自觉性确实高了许多,以前我看不懂的地方会跳过去,也许就放任自己不会,但是现在不知道出于什么原因,我对一些知识点和程序BUG的把关也渐渐的高了起来,反正坚持下去,总会有好结果的。最后还是祝福祖国,武汉加油!