golang做网站,网站多久备案一次,互联网设计是做什么的,烟台网站seo外包#x1f308;这篇blog记录一下指针学习~ 主要是关于指针和数组之间的关系#xff0c;还有指针的使用等~ #x1f34e;指针变量是一个变量 其本身也有一个地址 也需要存放#xff0c;就和int char等类型一样的#xff0c;也需要有一个地址来存放它 #x1f34c;而指针变量… 这篇blog记录一下指针学习~ 主要是关于指针和数组之间的关系还有指针的使用等~ 指针变量是一个变量 其本身也有一个地址 也需要存放就和int char等类型一样的也需要有一个地址来存放它 而指针变量 是用于存储其他变量的地址 int char 等类型存储的是对应的值比如 1 2 3 ‘a’ ‘b’等但是指针变量存储的就是地址 可以通过解引用“ * ”来获取指针变量地址的变量对应的值 比如 ptr1是指向utn[0]的指针那么 ptr1存放的就是utn[0]的地址,我通过解引用就可获取 utn[0]存储的值是多少。 文章目录 指针和数组函数 数组和指针使用指针形参指针的操作和野指针指针的操作赋值解引用取址相加递增指针指针减去一个整数递减指针指针求差比较 关于野指针 指针和数组
flizny flizny[0]; // 数组名是该数组首元素的地址flizny 和flizny[0]都表示数组首元素的内存地址是地址运算符。两者都是常量在程序的运行过程中不会改变。
但是可以把它们赋值给指针变量然后可以修改指针变量的值。注意指针加上一个数时它的值发生了什么变化转换说明%p通常以十六进制显示指针的值。
我们的系统中地址按字节编址short类型占用2字节double类型占用8字节。在C中指针加1指的是增加一个存储单元。对数组而言这意味着把加1后的地址是下一个元素的地址而不是下一个字节的地址。只知道地址不够因为计算机要知道储存对象需要多少字节即使指针指向的是标量变量也要知道变量的类型否则*pt 就无法正确地取回地址上的值
int dates[y], *pti;
pti dates; // orpti dates [0];指针的值是它所指向对象的地址。地址的表示方式依赖于计算机内部的硬件。 许多计算机包括PC和Macintosh都是按字节编址意思是内存中的每个字节都按顺序编号。这里一个较大对象的地址如double类型的变量通常是该对象第一个字节的地址。
在指针前面使用*运算符可以得到该指针所指向对象的值。 指针加1指针的值递增它所指向类型的大小以字节为单位。 下面的等式体现了C语言的灵活性
dates 2 date[2] // 相同的地址* (dates 2) dates[2] // 相同的值以上关系表明了数组和指针的关系十分密切可以使用指针标识数组的元素和获得元素的值。 从本质上看同一个对象有两种表示法。实际上C语言标准在描述数组表示法时确实借助了指针。也就是说定义ar[n]的意思是 * (ar n)。可以认为 *(ar n)的意思是“到内存的ar位置然后移动n个单元检索储存在那里的值”。
*(dates 2) // dates第3个元素的值
*dates 2 // dates第1个元素的值加2#include stdio.h
#define MONTHS 12
int main(void)
{int days[MONTHS] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };int index;for (index 0; index MONTHS; index)printf(Month %2d has %d days.\n, index 1,*(days index)); //与 days[index]相同return 0;
}
这里days是数组首元素的地址days index是元素days[index]的地址 而 * (days index)则是该元素的值相当于days[index]。for循环依次引用数组中的每个元素并打印各元素的内容。
这样编写程序是否有优势不一定。编译器编译这两种写法生成的代码相同。要注意的是指针表示法和数组表示法是两种等效的方法。该例演示了可以用指针表示数组反过来也可以用数组表示指针。 在使用以数组为参数的函数时要注意这点。
函数 数组和指针
关于函数的形参还有一点要注意。只有在函数原型或函数定义头中才可以用int ar[]代替int * ar
int sum (int ar[], int n);
*int ar形式和int ar[]形式都表示ar是一个指向int的指针。但是int ar[]只能用于声明形式参数。第2种形式int ar[]提醒读者指针ar指向的不仅仅一个int类型值还是一个int类型数组的元素。
因为数组名是该数组首元素的地址作为实际参数的数组名要求形式参数是一个与之匹配的指针。只有在这种情况下C才会把int ar[]和int * ar解释成一样。也就是说ar是指向int的指针。由于函数原型可以省略参数名所以下面4种原型都是等价的
int sum(int *ar, int n);
int sum(int *, int);
int sum(int ar[], int n);
int sum(int [], int);但是在函数定义中不能省略参数名
关于sizeof 的占位符符号 %zd
如果编译器不支持 %zd用 %u 或 %lu 替换它
printf(The size of marbles is %zd bytes.\n,
sizeof marbles);关于指针和数组byte的问题
/ sum_arr1.c -- 数组元素之和
// 如果编译器不支持 %zd用 %u 或 %lu 替换它
#include stdio.h
#define SIZE 10
int sum(int ar[], int n);
int main(void)
{int marbles[SIZE] { 20, 10, 5, 39, 4, 16, 19, 26, 31, 20 };long answer;answer sum(marbles, SIZE);printf(The total number of marbles is %ld.\n, answer);printf(The size of marbles is %zd bytes.\n,sizeof marbles);return 0;
}int sum(int ar[], int n) // 这个数组的大小是
{int i;int total 0;for (i 0; i n; i)total ar[i];printf(The size of ar is %zd bytes.\n, sizeof ar);return total;
}/*该程序的输出如下
The size of ar is 8 bytes.
The total number of marbles is 190.
The size of marbles is 40 bytes.
注意marbles的大小是40字节。这没问题因为marbles内含10个int类型的值每个值占4字节所以整个marbles的大小是40字节。
但是ar才8字节。这是因为ar并不是数组本身它是一个指向 marbles 数组首元素的指针。我们的系统中用 8 字节储存地址所以指针变量的大小是 8字节其他系统中地址的大小可能不是8字节。简而言之在程序中marbles是一个数组 ar是一个指向marbles数组首元素的指针利用C中数组和指针的特殊关系可以用数组表示法来表示指针ar。
使用指针形参
/* 使用指针算法 */
int sump(int * start, int * end)
{int total 0;while (start end){total *start; // 把数组元素的值加起来start; // 让指针指向下一个元素}return total;
}
//也可以
total *start;
指针start开始指向marbles数组的首元素所以赋值表达式total *start把首元素20加给total。然后表达式start递增指针变量start使其指向数组的下一个元素。因为start是指向int的指针start递增1相当于其值递增int类型的大小。
total *start; 一元运算符 * 和的优先级相同但结合律是从右往左所以start先求值然后才是 * start。 也就是说指针start先递增后指向。
使用后缀形式即start而不是start意味着先把指针指向位置上的值加到total上然后再递增指针。如果使用 * start顺序则反过来先递增指针再使用指针指向位置上的值。 如果使用(* start)则先使用start指向的值再递增该值而不是递增指针。这样指针将一直指向同一个位置但是该位置上的值发生了变化。 虽然 * start的写法比较常用但是*(start)这样写更清楚
/* order.c -- 指针运算中的优先级 */
#include stdio.h
int data[2] { 100, 200 };int moredata[2] { 300, 400 };
int main(void)
{int * p1, *p2, *p3;p1 p2 data;p3 moredata;printf( *p1 %d, *p2 %d, *p3 %d\n,*p1, *p2, *p3);printf(*p1 %d, *p2 %d, (*p3) %d\n,*p1, *p2, (*p3));printf( *p1 %d, *p2 %d, *p3 %d\n,*p1, *p2, *p3);return 0;
}
想想打印出来最终结果是什么 下面是该程序的输出
*p1 100, *p2 100, *p3 300
*p1 100, *p2 200, (*p3) 300
*p1 200, *p2 200, *p3 301只有(*p3)改变了数组元素的值其他两个操作分别把p1和p2指向数 组的下一个元素。 首先p1 他是直接指向我们数组的首地址解引用之后自然是数组的第一个数据 p2 p3也是一样的 ☘️ 其次 * p1 也就是 先执行指针start先递增后指向。使用后缀形式即p1而不是p1所以此时还是100 等这条命令结束之后* p1就是200了。而*p2 他是前缀形式所以不用等命令完成直接解引用得到200 p3的例子和p1类似 先解引用得到300 然后后缀递增也就是加一但是也是得等这条命令结束以后 最后一行 就是上面的p1 和p3结束以后得到的值而p2保持不变 至于C语言ar[i]和*(ar1)这两个表达式都是等价的。无论ar是数组名还是指针变量这两个表达式都没问题。但是只有当ar是指针变量时才能使用ar这样的表达式。
指针的操作和野指针
下面的程序示例中演示了8种不同的操作。为了显示每种操作的结果该程序打印了指针的值该指针指向的地址、储存在指针指向地址上的值以及指针自己的地址。 如果编译器不支持%p 转换说明可以用%u 或%lu 代替%p如果编译器不支持用%td转换说明打印地址的差值可以用%d或%ld来代替
// ptr_ops.c -- 指针操作
#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);printf(ptr1 %p, *ptr1 %d, ptr1 %p\n, ptr1, *ptr1, ptr1);
// 指针加法ptr3 ptr1 4;printf(\nadding an int to a pointer:\n);printf(ptr1 4 %p, *(ptr1 4) %d\n, ptr1 4, *(ptr1 4));ptr1; // 递增指针printf(\nvalues after ptr1:\n);printf(ptr1 %p, *ptr1 %d, ptr1 %p\n, ptr1, *ptr1, ptr1);ptr2--; // 递减指针printf(\nvalues after --ptr2:\n);printf(ptr2 %p, *ptr2 %d, ptr2 %p\n, ptr2, *ptr2, ptr2);--ptr1; // 恢复为初始值ptr2; // 恢复为初始值printf(\nPointers reset to original values:\n);printf(ptr1 %p, ptr2 %p\n, ptr1, ptr2);// 一个指针减去另一个指针printf(\nsubtracting one pointer from another:\n);printf(ptr2 %p, ptr1 %p, ptr2 - ptr1 %td\n, ptr2, ptr1, ptr2 - ptr1);// 一个指针减去一个整数printf(\nsubtracting an int from a pointer:\n);printf(ptr3 %p, ptr3 - 2 %p\n, ptr3, ptr3 - 2);return 0;
}对于以上的程序可以运行一下
指针的操作
赋值
可以把地址赋给指针。例如用数组名、带地址运算符的 变量名、另一个指针进行赋值。 在该例中把urn数组的首地址赋给了ptr1 该地址的编号恰好是0x7fff5fbff8d0。变量ptr2获得数组urn的第3个元素 urn[2]的地址。注意地址应该和指针类型兼容。也就是说不能把 double类型的地址赋给指向int的指针至少要避免不明智的类型转换。 C99/C11已经强制不允许这样做。
解引用
“ * ” 运算符**给出指针指向地址上储存的值。*因此 ptr1的初值是 100该值储存在编号为0x7fff5fbff8d0的地址上。
取址
和所有变量一样指针变量也有自己的地址和值。对指针而言 运算符给出指针本身的地址。本例中ptr1 储存在内存编号为 0x7fff5fbff8c8 的地址上该存储单元储存的内容是0x7fff5fbff8d0即urn的地 址。因此**ptr1是指向ptr1的指针**而ptr1是指向utn[0]的指针。 指针变量是一个变量 其本身也有一个地址 也需要存放就和int char等类型一样的也需要有一个地址来存放它 而指针变量 是用于存储其他变量的地址 int char 等类型存储的是对应的值比如 1 2 3 ‘a’ ‘b’等但是指针变量存储的就是地址 可以通过解引用“ * ”来获取指针变量地址的变量对应的值 比如 ptr1是指向utn[0]的指针那么 ptr1存放的就是utn[0]的地址,我通过解引用就可获取 utn[0]存储的值是多少。 相加
指针与整数相加可以使用运算符把指针与整数相加或整数与指针 相加。无论哪种情况整数都会和指针所指向类型的大小以字节为单位 相乘然后把结果与初始地址相加。因此ptr1 4与urn[4]等价。如果相加 的结果超出了初始指针指向的数组范围计算结果则是未定义的。除非正好 超过数组末尾第一个位置C保证该指针有效。
递增指针
递增指向数组元素的指针可以让该指针移动至数组的下一个 元素。因此ptr1相当于把ptr1的值加上4我们的系统中int为4字节 ptr1指向urn[1]见图10.4该图中使用了简化的地址。现在ptr1的值是 0x7fff5fbff8d4数组的下一个元素的地址*ptr的值为200即urn[1]的 值。注意ptr1本身的地址仍是 0x7fff5fbff8c8。毕竟变量不会因为值发 生变化就移动位置。
指针减去一个整数
可以使用-运算符从一个指针中减去一个整数。指针必须是第1个运算对象整数是第 2 个运算对象。该整数将乘以指针指向类型的大小以字节为单位然后用初始地址减去乘积。所以ptr3 - 2与urn[2]等价因为ptr3指向的是arn[4]。如果相减的结果超出了初始指针所指向数组的范围计算结果则是未定义的。除非正好超过数组末尾第一个位置C保证该指针有效。
递减指针
当然除了递增指针还可以递减指针。在本例中递减ptr3使其指向数组的第2个元素而不是第3个元素。前缀或后缀的递增和递减运算符都可以使用。注意在重置ptr1和ptr2前它们都指向相同的元素urn[1]。
指针求差
可以计算两个指针的差值。通常求差的两个指针分别指向同一个数组的不同元素通过计算求出两元素之间的距离。 差值的单位与数组类型的单位相同。例如ptr2 - ptr1得2意思是这两个指针所指向的两个元素相隔两个int而不是2字节。只要两个指针都指向相同的数组或者其中一个指针指向数组后面的第 1 个地址C 都能保证相减运算有效。如果指向两个不同数组的指针进行求差运算可能会得出一个值或者导致运行时错误。
比较
使用关系运算符可以比较两个指针的值前提是两个指针都指向相同类型的对象。
在递增或递减指针时还要注意一些问题。编译器不会检查指针是否仍指 向数组元素。C 只能保证指向数组任意元素的指针和指向数组后面第 1 个位 置的指针有效。但是如果递增或递减一个指针后超出了这个范围则是未 定义的。 另外可以解引用指向数组任意元素的指针。但是即使指针指向 数组后面一个位置是有效的也能解引用这样的越界指针。
关于野指针
说到注意事项一定要牢记一点千万不要解引用未初始化的指针。例 如考虑下面的例子
int * pt;// 未初始化的指针
*pt 5; // 严重的错误为何不行第2行的意思是把5储存在pt指向的位置。但是pt未被初始化其值是一个随机值所以不知道5将储存在何处。这可能不会出什么错也可能会擦写数据或代码或者导致程序崩溃。
切记创建一个指针时系统只分配了储存指针本身的内存并未分配储存数据的内存。因此在使用指针之前必须先用已分配的地址初始化它。 例如可以用一个现有变量的地址初始化该指针使用带指针形参的函数时就属于这种情况。 无论如何使用指针时一定要注意不要解引用未初始化的指针 ☘️笔记源于 C Primer Pluse 第六版