1.内存和地址
为了有效管理内存空间 ,将内存划分为一个个的内存单元,每个内存单元取一个字节
常见单位(补充)
1byte=8bit
1KB=1024byte
1MB=1024KB
1GB=1024MB
1TB=1024MB
1PB=1024TB
在计算机中中,我们也把内存的编号叫做地址,C语言中,地址也被称为“指针”
可以理解为:内存的编号==地址==指针
2.指针变量和地址
2.1取地址操作符(&)
在C语言中创建变量就是向内存中申请空间
上述代码就是创建了整型变量啊,内存中申请了4个字节的空间用于存放10
每个字节都有地址
int main()
{
int a = 10;
return 0;
}
0xFFD70
0xFFD71
0xFFD72
0xFFD73
&就是用来得到a地址的操作符
int main()
{
int a = 10;
&a;
printf("%p\n",&a);
return 0;
}
上述代码会打印处理:012FF89C
2.2指针变量和解引用操作符(*)
2.2.1指针变量
我们通过取地址操作符得到的地址,有时候会后期使用,为了方便后期使用,应该存储起来,我们会把地址值存放在指针变量中
2.2.2如何拆解指针类型
int a=10;
int * pa=&a;
*是说明pa是指针变量,int说明pa指向的是整型
char ch=‘w’;
char pc=&ch;
char类型也同理
2.2.3解引用操作符
我们保存了地址,如何使用呢
在C语言中,我们只要拿到了地址就可以通过地址找到地址所指向的对象
这时需要运用到解引用操作符(*)
int main()
{
int a = 10;
int *p=&a;
*p = 0;
return 0;
}
*p的意思就是通过p中存放的地址找到所指向的空间,*p就是a;这个操作就是将a变量改为0;
2.3指针变量的大小
int main()
{
printf("%zd", sizeof(char*));
printf("%zd", sizeof(int*));
printf("%zd", sizeof(short*));
printf("%zd", sizeof(double*));
return 0;
}
在32位平台下是32个bite位,指针变量大小是4个字节
在64位平台下是64个bite位,指针变量大小是8个字节
注意:指针变量的大小与类型无关,在相同平台下,大小都是相同的
3.指针变量类型的意义
3.1指针的解引用
int main()
{
int n = 0x11223344;
int* pi = &n;
*pi = 0;
return 0;
}
int main()
{
char n = 0x11223344;
char* pi = &n;
*pi = 0;
return 0;
}
通过调试可以看到,代码1将n的4个字节全部改为0;代码2将n的第一个字节改为0;
结论:指针类型决定了对指针解引用的权限(一次可以操作多少个字节)
比如:char类型就只能操作一个字节,而int类型能操作4个字节
3.2指针+-整数
int main()
{
char n = 0x11223344;
char* pi = &n;
int* pc = &n;
printf("%p\n", &n);
printf("%p\n",pi);
printf("%p\n",pi+1);
printf("%p\n", pc);
printf("%p\n",pc+1);
return

我们可以得出:指针的的类型决定了指针向前或向后走的距离
char类型的指针变量+1只跳过了1个字节;而int类型的指针变量跳过了4个指针类型
3.3void*指针
在指针类型中有一种叫void*类型的指针,是无具体类型的指针(泛型指针);这种指针可以接受任何类型的指针,但不能进行指针+-整数和指针解引用的运算

在上述代码中 char类型的变量的地址赋给int ,编译器给出不兼容的警告,如果用void*就不会出现警告

我们可以看到,void*类型可以接受不同类型的地址,但不能进行解引用运算
一般void*类型用来函数参数部分,用来接受不同类型的地址,可以实现泛型编程的效果,使得一个函数来处理多种类型的数据。
4.const修饰指针
4.1const修饰变量
变量是可以修改的,把变量地址交给一个指针变量,通过指针变量也可以修改,如果不想变量被修改,应该加以限制

上述代码中 n是不可以被修改的,被const修饰后 ,在语法上发生了限制,但是可以通过n的地址去修改。

4.2const修饰指针变量
一般来说const修饰指针变量,可以放在左边也可以放在右边,但是意义是不一样的。
int *p;
int const * p;
int * const p;
void test1()
{
int m = 0;
int n = 0;
int* p = &n;
*p = 10;
p = &m;
}
void test2()
{
int m = 0;
int n = 0;
int const* p = &n;
*p = 10;
p = &m;
}
void test3()
{
int m = 0;
int n = 0;
int* constp = &n;
*p = 10;
p = &m;
}
void test4()
{
int m = 0;
int n = 0;
int const* const p = &n;
*p = 10;
p = &m;
}
int main()
{
test1();
test2();
test3();
test4();
}
结论:const修饰指针变量时,const放在*左边,修饰的是指针指向的内容,保证指针指向的内容不会改变,但是指针变量本身的内容可以改变,const放在*右边,修饰的是指针变量本身,保证指针变量的内容不会改变,但是指针指向的内容可以改变。
5.指针运算
5.1指针+-整数运算
数组在内存中是连续存放的,只要知道第一个元素的地址,就能找到其他元素。
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,10,9};
int* p = arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz;i++)
{
printf("%d\n",*(p+i));
}
return 0;
}
5.2指针-指针
int my_strlen(char* s)
{
char* p = s;
while(*p!='\0')
p++;
return p - s;
}
int main()
{
printf("%d\n", my_strlen("abc"));
return 0;
}
5.3指针的关系运算
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
while (p < arr + sz)
{
printf("%d ", *p);
p++;
}
return 0;
}
6.野指针
概念:野指针就是指针指向的位置是不可知的
6.1野指针成因
1.指针未初始化
int main()
{
int* p;
*p = 20;
return 0;
}
2.指针越界访问
int main()
{
int arr[10] = {0};
int* p = arr;
int i = 0;
for (i = 0; i <= 11; i++)
{
*(p++) = i;
}
return 0;
}
3。指针指向空间释放
int* test()
{
int n = 100;
return &n;
}
int main()
{
int* p = test();
printf("%d\n", *p);
return 0;
}
6.2如何规避野指针
6.2.1指针初始化
如果明确知道指针指向哪里就直接赋值地址,如果不知道就可以给指针赋值NULL;NULL是C语言中定义的一个标识符常量,值是0;0也是地址,但是这个地址无法使用,读写该地址会报错。
int main()
{
int num = 10;
int* p = #
int* c = NULL;
return 0;
}
6.2.2小心指针越界
一个内存申请了哪些空间,通过指针就会访问哪些空间,不能超出范围
6.2.3指针不再使用时,及时置NULL,指针使用之前检查其有效性
当指针变量指向一块区域时,我们可以通过指针访问该区域后期不再使用这个指针时,将该指针置为NULL.
int main()
{
int arr[10] = {0};
int* p = arr;
int i = 0;
for (i = 0; i <= 11; i++)
{
*(p++) = i;
}
p = NULL;
p = &arr[0];
if (p != NULL)
{
}
return 0;
}
6.2.4避免返回局部变量的地址
7.assert断言
assert.h头文件定义了宏assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止,这个宏通常被叫做”断言“
assert(p!=NULL);
上述代码如果确实不等于,代码继续运行,否则就会报错终止。
assert 在确定已经不需要断言时可以在#include<assert.h>前面加上NDEBUG
如果程序再一次出现问题,将NDEBUG注释掉。
8.指针的使用和传址使用
8.1strlen的模拟实现
库函数strlen的功能时求字符串长度
int my_strlen(const char * s)
{
int c = 0;
assert(s);
while (*s)
{
c++;
s++;
}
return c;
}
int main()
{
int len = my_strlen("abdsjjdvkds");
primtf("%d\n", len);
return 0;
}
8.2传值调用和传址调用
void sw(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d",&a,&b);
printf("%d\n",a,b);
sw();
printf("%d\n",a,b);
return 0;
}
这种调用方式叫传值调用
sw中x和y的值并不会影响主函数中a和b的值的变化
结论:实参传给形参时,形参会单独创建一个临时空间来接收实参,对形参的修改不影响实参。
void Swap(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\n b=%d\n",a,b);
Swap(&a,&b);
printf("a=%d\n b=%d\n",a,b);
return 0;
}
这种函数调用叫传址调用
传址调用可以让函数与主调函数直接产生真正的联系
&spm=1001.2101.3001.5002&articleId=157735521&d=1&t=3&u=a5a1df1627d84aaf9e66305605e53d23)
1080

被折叠的 条评论
为什么被折叠?



