一、内存和指针
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* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
尽量不使用局部变量的地址。
<2>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;
}
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;
}