大纲
本文先说明一些指针的概念及一些简单应用,更详细的将在(下)说明
正文开始
指针的基础知识
什么是指针
以下来自百度百科:指针,在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
我们可以这样来理解:
定义一个指针:int *p; p为一个变量,用来存放某块内存的地址,如下图
例:
#includ<stdio.h>
int main()
{
int a=10;//在内存中开辟了一块空间,存放a
int *p=&a;//取出a得地址,将a的地址保存在p中,p即为指针变量
//注意:p才是变量,不是*p;
return 0;
}
总之:指针也是一个变量,是用来存放地址的。
指针存在的意义
抛砖引玉,在计算机内部,内存分成很多个小的单元,每个单元都对应一个独一无二的地址,这
样就一个地址标示一块空间,这样来说计算机的硬件指令可以依赖地址来完成,程序员也能够 以类似于计算机底层的表达方式来表达自己的意愿,这可以使得使用了指针的程序可以更高效的工作。
那么,问题来了,
1.一个小的单元是多大?(一个字节)
2.如何编址?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号
(0或者1),那么32根地址线产生的地址就会是
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
…
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。
每个地址标示一个字节,那我们就可以给4G的空闲进行编址。
同样,对于64位的机器,可以标示 2的64次方地址。
经过以上理解,我们可以得出两个结论
例:
2. 在64位的机器上,那么一个指针变量的大小要用8个字节来存储
总结
指针是存放地址才出现的,地址是为了标示一块地址空间的。
指针让地址有地方存放,指针让内存的访问更加方便。
指针的大小在32位平台是4个字节,在64位平台是8个字节
指针和指针类型
来看代码:
int num=10;
int *p=#
那如果写成这样
int num=10;
char *p=#
会报错,指针也是有类型的,指针的类型要与它所指向的内容相匹配,举几个例子:
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
指针的类型是:type+*的方式;
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
…
以此类推。
为什么这样做:
只要有类型的区分,就定出一定的规则,使得编码会更加严谨。
确定了指针运算的规律。
那么:指针类型如何确定指针运算的规律:
1.指针+-整数;
2.指针的解引用;
指针+-整数
示例程序
#include <stdio.h>
int main()
{
int num = 10;
char *pc = (char *)#
int *pi = #
printf("%p\n", &num);
printf("%p\n", pc);
printf("%p\n", pc + 1);
printf("%p\n", pi);
printf("%p\n", pi + 1);
return 0;
}
第一行:即为num的地址;
第二行:因为pc指针指向num,所以pc的输出和第一行相同;
第三行:在定义中,将&num强转为char*类型,pc加1即加一个char的大小,即为加1;
第四行:因为pi指针指向num,所以pi的输出和第一行相同;
第五行:pi+1;因为pi为int*类型,加1相当于加int的长度,即加4。
可以这样说:指针的类型决定了指针向前或者向后走一步有多大(距离)。
指针的解引用:
我们都知道 int*p=&num,那么这个*号是什么呢;怎么理解呢;
引用《c语言深度剖析》中的文章;
4.1.2,“*”与防盗门的钥匙 这里这个“*”号怎么理解呢?举个例子:当你回到家门口时,你想进屋第一件事就是 拿出钥匙来开锁。那你想想防盗门的锁芯是不是很像这个“*”号?你要进屋必须要用钥匙, 那你去读写一块内存是不是也要一把钥匙呢?这个“*”号就是不是就是我们最好的钥匙? 使用指针的时候,没有它,你是不可能读写某块内存的。
#include <stdio.h>
int main()
{
int num = 0x11223344;
char *pc = (char *)#
char *pi = #
*pc = 0x55;//重点在调试的过程中观察内存的变化。
*pi = 0; //重点在调试的过程中观察内存的变化。
return 0;
}
总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如:char*的指针解引用就只能访问一个字节,而int*的指针的解引用就能访问四个字
节。
二级指针
指针也是一个变量,那存放指针这个变量的地址在哪呢,怎么找到呢,我们这里引入二级指针的概念
a的地址存放在p中,p的地址存放在pp中,pp就是二级指针。 解引用一次*pp得到的是p,解引用两次**pp得到的是a,即访问的是a。
int b=20;
*pp=&b;//等价于p=&b;
**pp先通过*pp找到p,然后对p进行解引用操作:*p,那找到的是a.
**pp=30;//等价于把30赋给a。
那接下来看一个代码
char ch = 'a';
char *cp = &ch;
这是正确的,那什么类型的数据可以做左值,什么类型的数据可以做右值,判断的标准是什么呢?
先下结论,左值是空间,右值是内容,记住这两条规则,就容易分得清了。
指针运算
指针的运算可以分为以下三种:
define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
指针-指针
int my_strlen(char *s)
{
char *p = s;
while(*p != ‘\0’ )
p++;
return p-s;
}
关系运算
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*–vp = 0;
}
指针的一些关系运算
模拟实现strlen
方式一
int my_strlen(const char * str)
{
int count = 0;
while(*str)
{
count++;
str++;
}
return count;
}
方式二
int my_strlen(const char * str)
{
if(*str == ‘\0’)
return 0;
else
return 1+my_strlen(str+1);
}
方式三
int my_strlen(char *s)
{
char *p = s;
while(*p != ‘\0’ )
p++;
return p-s;
}
-模拟实现strcpy
char *my_strcpy(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while((*dest++ = *src++))
{
;
}
return ret;
}
模拟实现strcat
char *my_strcat(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while(*dest)
{
dest++;
}
while((*dest++ = *src++))
{
;
}
return ret;
}
模拟实现strstr
char *my_strstr(const char* str1, const char* str2 )
{
assert(str1);
assert(str2);
char *cp = (char*)str1;
char *substr = (char *)str2;
char *s1 = NULL;
if(*str2 == '\0')
return NULL;
while(*cp)
{
s1 = cp;
substr = str2;
while(*s1 && *substr && (*s1 == *substr))
{
s1++;
substr++;
}
if(*substr == '\0')
return cp;
cp++;
}
}
模拟实现strcmp
int my_strcmp (const char * src, const char * dst)
{
int ret = 0 ;
while( ! (ret = *(unsigned char *)src - *(unsigned char
*)dst) && *dst)
++src, ++dst;
if ( ret < 0 )
ret = -1 ;
else if ( ret > 0 )
ret = 1 ;
return( ret );
}
模拟实现memcpy
void * memcpy ( void * dst, const void * src, size_t count)
{
void * ret = dst;
/*
* copy from lower addresses to higher addresses
*/
while (count--) {
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
return(ret);
}
模拟实现memmove
void * memmove ( void * dst, const void * src, size_t count)
{
void * ret = dst;
if (dst <= src || (char *)dst >= ((char *)src + count)) {
/*
* Non-Overlapping Buffers
* copy from lower addresses to higher addresses
*/
while (count--) {
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
}
else {
/*
* Overlapping Buffers
* copy from higher addresses to lower addresses
*/
dst = (char *)dst + count - 1;
src = (char *)src + count - 1;
while (count--) {
*(char *)dst = *(char *)src;
dst = (char *)dst - 1;
src = (char *)src - 1;
}
}
return(ret);
}
参考资料 《c语言深度剖析》
【END】