个人简介:大数据本科在读
个人专栏:C语言系统学习
CSDN主页:Monodye
每日鸡汤:永远相信美好的事情即将发生
在讲内存之前我们先来举这样一个例子,在大学里我们每个人都有自己的寝室,如果你的导员想找一个人的时候,那么他会先去找这个人在哪个公寓,那层楼,哪个寝室,这样就可以很快速大的 完成这个在存在大量数据时快速查找的问题。
而计算机的内存我们常见的有8GB,16GB,32GB,等等在这么大的内存中,数据其实也是类似于我们上面说的这样,内存会编号,当我们需要调用内存中的一些数据的时候,就会很快的找到需要的数据,通常计算机的内存以一个字节为最小单位,并对其进行编号。而这个编号就是这个字节里存放内容的地址。
理解了内存和地址之后,我们来说说C语言里的创建变量时内存的操作:
【 比如这个简单的代码,他其实是先创建了一个a变量,在内存中开辟了一个大小为4个字节的空间,对齐进行编号,并把a的值存放进去。】
那么我们如何获取a的地址呢,那么我们需要学习一个符号“&”(取地址操作符),
printf("%p",&a);
这样就会返回a四个字节的第一个字节,因为内存是连续存放的,所以我们只需要直到他的第一个字节,后面的地址就可以顺藤摸瓜进行访问。
经过我们上面的学习我们可以用&符号将一个变量的地址拿到,有时我们也需要把这个地址存放起来,这时我们就需要使用指针变量将其存放起来
而 int* ,中*说明p是一个指针变量,而int说明p返回的是整形数据。
我们说解引用操作符(&),可以取一个数据的地址,而指针又可以存放一个数据的地址,所以这两者经常一起使用
int a=10;
int*p=&a;//表示将a的地址传给了指针p
关于指针变量的大小,我们常常会根据机器32位或64位进行讨论,计算机里存放的是二进制,所以当32位转化位为地址时需要32个bite位,即4个字节。64位即64个bite位需要8个字节。
所以如果【指针变量要存放地址的话那么他至少需要4个字节】,注意指针变量的大小与其类型无关,无论是什么类型都是4或8个字节,而它的类型影响的是指针变量一次可以操作的最大限度,这个以后再说。
既然每个指针变量的类型都是4或8个字节,那么这么多的指针类型又有什么用呢,仔细观察下面两个代码:
//代码1
#include
int main()
{
int n = 0x11223344;
int *pi = &n;
*pi = 0;
return 0;
}
//代码2
#include
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
*pc = 0;
return 0;
}
运行这两个代码你会发现,第一个代码会将n的值修改为0,而第二个代码则将会把n的值修改为0x11223300我们猜测:
- int*的指针可以访问数据四个字节所以第一个代码将n修改为0
- char*的指针可以访问一个字节所以n的值修改了一个字节0x11223300
总结:
指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
下面给大家介绍一个特殊的指针void*指针,它可以存放各种类型的地址,但是他有一个特点:
不能直接进行指针的+-整数,以及解引用(&)操作。
#include
int main()
{
int a = 10;
void* pa = &a;
void* pc = &a;
*pa = 10;
*pc = 0;
return 0;
}
例如这个代码就会报错,因为对void*进行了解引用的操作。
我们知道变量的值是可以进行修改的,变量将地址传给指针,通过指针也可以修改这个变量,那么如果我们想给变量加一个修饰,使它的值不可被修改是否可以呢,答案是肯定的:
#include
int main()
{
int m = 0;
m = 20;//m是可以修改的
const int n = 0;
n = 20;//n是不能被修改的
return 0;
}
经过const修饰后,n的值就不可以再发生变化,但如果我们同过指针间接的修改n的值其实也是可以的,但是这打破了语法规则,不建议使用,比如这样:
#include
int main()
{
const int n = 0;
printf("n = %d\n", n);
int*p = &n;
*p = 20;
printf("n = %d\n", n);
return 0;
}
那么是否可以将指针的值也固定住呢,运行下面代码:
#include
//代码1
void test1()
{
int n = 10;
int m = 20;
int *p = &n;
*p = 20;//ok?
p = &m; //ok?
}
void test2()
{
//代码2
int n = 10;
int m = 20;
const int* p = &n;
*p = 20;//ok?
p = &m; //ok?
}
void test3()
{
int n = 10;
int m = 20;
int *const p = &n;
*p = 20;
p = &m; //ok?
}
void test4()
{
int n = 10;
int m = 20;
int const * const p = &n;
*p = 20; //ok?
p = &m; //ok?
}
int main()
{
//测试⽆const修饰的情况
test1();
//测试const放在*的左边情况
test2();
//测试const放在*的右边情况
test3();
//测试*的左右两边都有const
test4();
return 0;
}
我们可以得到以下结论:
野指针:即指针指向的地址是随机的,不确定的。(非常危险)
1.指针未初始化:
#include
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
2.指针访问越界 :
#include
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
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
assert()的作用是判断括号内的语句是否符合条件,如果不符合就会终止运行报错,可以用于程序的检验。
使用时要加:
#include
注意:Debug版本下,可以使用assert断言,而release版本下,直接被优化,这是为用户的体验效率考虑的。
#include
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);
printf("交换前:a=%d b=%d\n", a, b);
Swap1(a, b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
运行一下;
#include
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);
Swap1(&a, &b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
ab的值交换成功。