指针的理解系列--1

目录

一、指针变量和地址

1、地址

1.1  取地址操作符--&

1.2  解引用操作符--*

1.3  指针p的三个相关值

2、指针变量

2.1 指针变量的大小

2.2 指针的类型

Ⅰ、指针的类型决定了,对指针解引用的时候有多大权限(一次可以操作几个字节)

Ⅱ、指针的类型决定了指针向前或向后走一步的距离

Ⅲ、void*指针

2.3 const 修饰指针

2.4 指针的运算

Ⅰ、指针 +- 整数

Ⅱ、指针-指针

2.5 野指针

Ⅰ、野指针的成因

(1.1) 指针未初始化

(1.2) 指针越界访问

(1.3) 指针指向的空间释放

Ⅱ、如何规避野指针

(2.1) 指针初始化

(2.2) 使用前检查

(2.3) 指针使用前检查有效性

Ⅲ、assert断言

2.6 指针的使用

2.7 传址调用

2.8 传值调用


一、指针变量和地址

1、地址

在C语言中,地址==指针

1.1  取地址操作符--&

&

#include<stdio.h>
int main()
{
int a = 10;
&a; //取出a的地址
printf("%p \n",&a);
    //%p是打印地址的
return 0;
}
1.2  解引用操作符--*

我们通过上面的取地址操作符(&)拿到的地址是一个数值【000000EEBB94F5C4】,这个数值需要被储存起来,方便后期使用,我们将该数值存放在指针变量中。

【需要注意的是取地址操作符,拿到的是变量第一个字节的地址】

【int 型】

#include<stdio.h>
int main()
{
int a = 10;
int * pa = &a; //取出a的地址并且存放在指针变量pa里
//pa是指针变量的名字
//【int *】是pa的类型
return 0;
}
int a = 10;
int * pa = &a;

pa的左边写的是int *  *表示pa是指针变量,前面的 int 表示pa指向的是整型(int)类型的对象。

【char 型】

char ch = 'w';
char * pc = &ch;

【指针变量是一种变量,用来存放地址】

1.3  指针p的三个相关值

[1] p , p里面放着一个地址

[2]*p , p指向的那个对象

[3]&p , 表示的是变量p的地址 

2、指针变量

2.1 指针变量的大小

一个地址需要32个bit位,需要4个字节储存。(1个字节=8个bit位)

sizeof :求某个类型创建变量占内存空间的大小

#include<stdio.h>
int main()
{
printf("%zd\n",sizeof(char *));     //4(x86)   or   8(x64)
printf("%zd\n",sizeof(short *));    //4(x86)   or   8(x64)
printf("%zd\n",sizeof(int *));      //4(x86)   or   8(x64)
printf("%zd\n",sizeof(double *));   //4(x86)   or   8(x64)
return 0;
}
//指针变量大小取决于地址的大小
//32位平台下地址是32个bit位(4个字节)  ---x86环境下
//64位平台下地址是64个bit位(8个字节)  ---x64环境下

【指针变量的大小与类型无关,与平台有关(x86:4 ---x64:8)】

2.2 指针的类型
Ⅰ、指针的类型决定了,对指针解引用的时候有多大权限(一次可以操作几个字节)

eg:  char* 的指针解引用只能访问1个字节

           int* 的指针解引用可以访问4个字节】

Ⅱ、指针的类型决定了指针向前或向后走一步的距离
#include<stdio.h>
int main()
{
int a = 0x11223344;
int * pa = &a;
char * pc = &a;

printf("&a=%p\n",&a);              //&a=0136FCF0
printf("&a+1=%p\n", &a + 1);     //&a+1=0136FCF4

printf("pa=%p\n",pa);              //pa=0136FCF0
printf("pa+1=%p\n",pa+1);        //pa+1=0136FCF4

printf("pc=%p\n",pc);              //pc=0136FCF0
printf("pc+1=%p\n",pc+1);        //pc+1=0136FCF1

return 0;
}

int*类型的指针变量+1跳过4个字节,char*类型的指针变量+1跳过1个字节.

Ⅲ、void*指针

可以把void*理解为无具体类型的指针(或叫泛型指针),这类指针可以用来接受任意类型地址,但这类型的指针不能直接进行指针的+-整数和解引用的运算。

当我们将一个int类型的变量的地址 赋值给 一个char*类型的指针变量时,编译器会给出警告,     因为类型不兼容,但使用void*就可避免。

#include<stdio.h>
int main()
{
int a = 10;
void* pa = &a;
void* pc = &a;
*p = 10;
*pc = 0;
return 0;
}
2.3 const 修饰指针
int const * p;  //const 放在*的左边
//左边:
//限制了指针指向的内容,不能修改它指向的那个变量里面的内容
//但是指针变量本身可以改变,它可以改变指向(指向别的变量)

int * const p;  //const 放在*的右边
//右边:
//限制的是指针变量本身,不能改变它的指向
//但是可以修改指针指向的那个变量里面的内容
#include<stdio.h>

void tset1()
{ //左边
int n = 10;
int m = 20;
const int * p = &n;
*p = 20;   //不可以
 p = &m;   //可以
}

void tset2()
{ //右边
int n = 10;
int m = 20;
int * const p = &n;
*p = 20;   //可以
 p = &m;   //不可以
}

void test3()
{ //左右两边都有
int n = 10;
int m = 20;
int * const p = &n;
*p = 20;   //不可以
 p = &m;   //不可以
}
2.4 指针的运算
Ⅰ、指针 +- 整数

数组在内存中是连续存放的,只要知道第一个元素的位置,就可以顺藤摸瓜找到后面的所有元素。

#include<stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int * p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for( i=0 ; i<sz ; i++)
{
printf("%d ", *(p+i) );
            //这里的p+i就是指针+整数
            //int * p;
            //p+i 是跳过i*sizeof(int)个字节
}

return 0;
}

Ⅱ、指针-指针

指针-指针的绝对值是指针和指针之间元素的个数。

指针-指针,计算前提,它们指向同一个空间。

【写一个函数,求字符串长度】

#include<stdio.h>
#include<string.h>
size_t my_strlen(char* p)
{
char * start = p;
char * end = p;
while(*end != '\0')
{
end++;
}
return end-start;
}
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);
printf("%zd\n",len);   //6
return 0;
}

2.5 野指针

野指针就是指针指向的位置是不可知的(随机的、不确定的、没有明确限制的)

Ⅰ、野指针的成因
(1.1) 指针未初始化
#include<stdio.h>
int main()
{
int *p;    //局部变量指针未初始化,默认为随机值
*p = 20;   //解引用操作符会形成非法访问
//此时p是野指针
return 0;  
}
(1.2) 指针越界访问
#include<stdio.h>
int main()
{
int arr[10] = {0};    
int *p = &arr[0];   
int i = 0;
for(i=0 ; i<=11 ; i++) 
{   //当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;  
}
(1.3) 指针指向的空间释放
#include<stdio.h>
int* test()
{
int n = 100;
return &n;
}

int main()
{ 
int *p = test();   
printf("%d\n",*p);
return 0;  
}
Ⅱ、如何规避野指针
(2.1) 指针初始化

如果明确知道指针指向哪里,就直接赋值相应的地址;如果不确定,就可以给指针赋值NULL。

【NULL是C语言中定义的一个标识符常量,值是0,0也是地址,但这个地址无法使用,读写它会报错。】

#include<stdio.h>
int main()
{
int num = 10;
int *p1 = &num;
int *p2 = NULL;
return 0;
}
(2.2) 使用前检查

【指针变量不再使用时,及时置NULL,指针使用之前检查有效性。】

当指针指向一块区域时,我们可以通过指针访问;后期不使用这个指针访问时,可以把指针置NULL。

只要是NULL指针就不去访问,同时使用指针前,要判断指针是否为NULL。

【if(p != NULL) { };】

#include<stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
for( i=0 ; i<10 ; i++ )
{
*(p++) = i;
}
//此时p已经越界,可以把它置为NULL
p = NULL;

//下次要使用它,先判断p不为NULL再使用
p = &arr[0];
//进行判断
if(p != NULL)
{
//....
}
return 0;
}
(2.3) 指针使用前检查有效性

Ⅲ、assert断言
assert.h头文件定义了宏assert( 判断条件 ),由于在运行时确保程序符合指定条件,若不符合,就报错终止运行。
【但在Release版本中,无论assert( )是否满足条件,都不会报错。】
2.6 指针的使用

例题(strlen的模拟实现)

#include<stdio.h>
#include<assert.h>
#include<string.h>
size_t my_strlen(const char* s)
{
size_t count = 0;
assert(s != NULL);   //监测指针s是否有效
while(*s)
{
count++;
s++;
}
return count;
}
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);
printf("%zd\n",len);   //6
printf("%s\n",arr);    //abcdef
}
2.7 传址调用

如果我们想写一个函数,交换两个整型变量的值

#include <stdio.h>
void Swap1(int x, int y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
int main()
{
  int a = 0;
  int b = 0;
	scanf("%d %d", &a, &b);   //10  20
	printf("交换前:a=%d b=%d\n", a, b);   //交换前:a=10 b=20
	Swap1(a, b);
	printf("交换后:a=%d b=%d\n", a, b);   //交换后:a=10 b=20
  return 0;
}

Swap1函数内部创建了形参x和y来接收a和b的值,

x和y确实收到了a和b的值,但x与a的地址不一样,b与y的地址也不一样,

相当于x和y是独立的空间,在Swap1函数内部交换x和y的值不会影响a和b。

Swap1函数在使用的时候,把变量本身直接传给了函数,这种调用函数的方式叫:传值调用

2.8 传值调用
#include <stdio.h>
void Swap2(int *pa, int *pb)
{
	int z = *pa;   //z=a;
	*pa = *pb;     //a=b;
	*pb = z;       //b=z;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);                //3   5
	printf("交换前:a=%d b=%d\n", a, b);   //a=3 b=5
	Swap2(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);   //a=5 b=3
	return 0;
}

这里调用Swap2函数时,是将变量的地址传递给函数,这种函数调用方式叫:传址调用

到这里指针理解系列1就结束啦!(未完待续哦)

猜你喜欢

转载自blog.csdn.net/2301_78136992/article/details/139032508