学习——理解指针(1)

一、内存和指针

        1、内存与地址

                举个例子,一栋大楼中有许多个房间,如果想要快速的找到一个房间,就需要知道这个 房间的门牌号,这个门牌号就可以理解成这个房间的地址;

                在计算机中其实也是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。
计算机中常⻅的单位(补充):
  • ⼀个⽐特位可以存储⼀个2进制的位1或者0;
  • 一个字节(byte)等于八个比特位(bit)。

                存储信息的内存编号 <==> 地址 <==> 指针

二、指针变量(&与*)

                在编写代码时:

                int a;        创建了一个整型变量a,向内存中申请了一个整型变量(占4个字节)的空间,每一个字节的内存空间都有它的内存编号。

          <1> &(取地址操作符)

                通过&(取地址操作符)可以获得一个变量的地址,&a就是a的地址;

                有了a的地址,现在需要把地址存起来,这时候就用到了指针变量;

int a;
int* b=&a;

                这里,b就是一个指针变量,类型是int*,*说明b是一个指针变量,int表示b所指向的对象类型是个整型。

          <2> *(解引用操作符)

                指针变量存放地址,如果想通过这个地址找到它指向的变量,就需要用到*(解引用操作),即 

int a=10;

int* b=&a;

printf("%d",*b);       

               通过解引用操作,就可以找到指针所指向的变量;

        指针变量可以存储一个变量的地址,但如果这个变量是一个指针变量,这是,就会一个新的概念:二级指针

        二级指针:存放一级指针地址的变量。

int a=10;
int* pa=&a;
int** ppa=&pa;

                   *pa=a。        **ppa=*pa=a。

三、指针类型的意义

        <1> 指针类型不同,通过解引用操作所访问从字节数就不同

例:char*类型的指针解引用操作只访问一个字节,二int*类型访问4个字节;

    int a = 1234443;  int* p = &a;  *p = 0;                

int a=1234443; char*pa=(char*)&a; *p=0;

        <2> 指针类型不同,指针运算所跳过的字节数也不同

char* 类型指针+1跳过1个字节,而int*类型的指针+1跳过4个字节。

        <3>void*指针

void是一个泛型指针,它可以接受任何类型的指针,但是不能进行指针运算和解引用操作。

四、指针运算

        <1>指针+-整数

        我们知道,数组在内存中是连续存储的,所以,只需要找到一个元素的地址,通过指针+-整数会可以访问数组中所有数据。

#include<stdio.h>
int main() {
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int i, sz;
	int* p = arr;
	sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++) {
		printf("%d ", *(p + i));
	}
	return 0;
}

用指针加减整数去实现数组下标访问。

        <2>指针-指针

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

前提:两个指针指向同一块内存空间。

#include<stdio.h>

int my_strlen(char* str) {
	char* p = str;
	while (*str != '\0') {
		str++;
	}
	return str - p;

}
int main() {
	char arr[] = "abcd";
	printf("%d\n", my_strlen(arr));
	return 0;
}

        <3>指针关系运算

指针地址大小的比较。

int main() {
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	while (p < arr + sz) {
		printf("%d ", *p);
		p++;
	}
	return 0;
}

五、const 修饰指针

        当const修饰一个变量时,这个变量就不可以再进行修改;现在,用const修饰一个指针变量,这个指针变量是不是也是不可被修改的呢?

        我们知道,指针变量存放的是地址,地址又有所指向的值,所以用const修饰指针变量,是指针变量不可被修改,还是指针所指向的值不可被修改呢?

        <1>const 修饰*p

        const int *p;

        const 放在*的前面(左面)时,const修饰指针所指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本身的内容可变(就是他存储的地址可以改变)。

        <2>const 修饰p

        int* const p;

        const如果放在*的后面(右边),修饰的是指针变量本身,保证了指针变量的内容不能修改(它存储的地址不能改变),但是指针指向的内容,可以通过指针改变。

六、野指针

        <1>野指针形成原因

        1.指针没有初始化

int* p;

*p=20;

规避:初始化指针;不知道指针该指向哪里,就给指针赋值NULL(空指针)。

 NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错。 

        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 ;
}
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是 越界访问。
        3.指针所指向的空间被释放
#include <stdio.h>
int* test()
{
 int n = 100;
 return &n;
}
int main()
{
 int*p = test();
 printf("%d\n", *p);
 return 0;
}

尽量不使用局部变量的地址。

        <2>assert断言

        指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性  
assert(p != NULL );
         用来判断指针是否为空指针,如果为空,代码程序终止。

        assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报
错终⽌运⾏。这个宏常常被称为“断⾔”。
        而如果不再需要assert进行断言
# define NDEBUG
使assert失效,不进行判断。

七、传值调用与传址调用

        当你用代码编写一个函数交换两个数的值时

void swap1(int x, int y) {
	int t;
	t = x;
	x = y;
	y = t;
}
int main() {
	int a, b;
	scanf("%d%d", &a, &b);
	printf("交换前:a=%d, b=%d", a, b);
	swap1(a, b);
	printf("交换后:a=%d, b=%d", a, b);
	return 0;
}

你会发现,这样并没有把a与b的值进行交换;

        其实,这样写代码只是将a与b的数值传给了函数swap1的形参,而在函数运行时,会创建两个临时变量x,y用来接收a,b的值,而函数也只是将x,y两个的值交换了,并没有把a和b进行交换;当函数执行完以后,a与b没有进行交换。

实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实
参。

像这种只是把数值传给函数形参的,就是传值调用

那又该怎样实现?

可以将a,b的地址传给函数,这样函数在运行工程中,可以直接通过解引用操作访问a与b,并进行修改

void Swap2(int*px, int*py)
{
 int tmp = 0;
 tmp = *px;
 *px = *py;
 *py = tmp;
}
int main()
{
 int a = 0;
 int b = 0;
 scanf("%d %d", &a, &b);
 printf("交换前:a=%d b=%d\n", a, b);
 Swap2(&a, &b);
 printf("交换后:a=%d b=%d\n", a, b);
 return 0;
}
传址调⽤:可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所 以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改 主调函数中的变量的值,就需要传址调⽤。
        应用:strlen的模拟实现
int my_strlen(const char * str)
{
 int count = 0;
 assert(str);
 while(*str)
 {
 count++;
 str++;
 }
 return count;
}
int main()
{
 int len = my_strlen("abcdef");
 printf("%d\n", len);
 return 0;
}
  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值