0、介绍
int a = 10;
定义了一个 int 变量 名字 a 把 10 赋值 给 a
变量 a 的值是10
给了一个 4字节的 存储空间,有个编号
a = 20;//把 20 放进 a 代表的那个空间//写入
printf("a = %d",a);//读出
找到存储 a 的那个空间在哪,就是找到那个编号,就是地址
1、指针
为了方便管理内存,按字节为单位进行编号的。
地址: 分配给每个变量的内存单元都有一个编号,编号就是我们所说的地址,在C语言中指针和地址差不多。
变量的地址/指针 是可以直接打印出来的,%p
&取地址符
&变量名 --->取该变量的地址
2、指针变量
用来保存“地址/指针”的 “变量”,一般是用来保存 其他变量的地址/指针。
指针变量的 定义语法:
指向的变量的类型 * 指针变量名 [= 它指向的那个变量的地址];
指向的变量 : 指针变量保存了谁的地址(内存编号),就说指针变量指向了谁
例:
int a = 10;
想要用 指针变量 来保存 a 的地址
就是想指向 a, a 的类型是 int
-----> int * p = &a;
* 是指针变量的声明符
int 是 a 的类型是
p 是指针变量,&a 取 a 的地址 是 p 的初始值。
练习:
初始化一个字符型的变量,定义一个指针变量指向它
char c = '1';
int a;// 没有初始化,a的值是什么 随机
char * p;// 没有初始化,p指向哪,不知道, 这个情况我们一般称为 “野指针”
p = &c;// 赋值之后, p 指向了变量c , 现在就不是 “野指针”
//这两条语句就相当于 char * p = &c;
3、与指针相关的两个运算符
&取地址符单目运算符
&对象名 ----> 取这个对象的地址
*解引用运算符单目运算符
*对象名 ---->地址对应的那个对象
int a = 10;
*(&a) <-->a
int * p = &a;
*p <--> *(&a) <-->a
例:
float b = 3.1415;
float * q = &b;//指针变量q 保存了 b 的地址, q 指向了 b
// *q <--> b
printf("%f\n",b);//打印 b 的值 3.1415
printf("%f\n",*q);//打印 b 的值 3.1415
*q = 2.048;//相当于给 b 赋值
printf("%f\n",b);//打印 b 的值 2.048
printf("%f\n",*q);//打印 b 的值 2.048
分析:
double d = 1.234;
double * p;// 野指针 避免出现野指针
*p = 5.4321;// 段错误内存非法访问 访问不到 *p
printf("%f\n",d);
printf("%f\n",*p);
4、空指针
如果一个指针变量 的值 为 NULL,就把这个指针称为空指针
int * p = NULL;//p 是空指针
//int *p;
//p = NULL;
*p = 100;// 和 野指针 一样, 也不能访问空指针指向的对象
练习:
写一个函数,用来交换两个整数的值
void swap1(int a,int b)//int a = 3,b = 5;
{
int t = a;
a = b;
b = t;//a = 5; b = 3; 临时变量 , 用完就gg, 而且和 主调函数实参没联系
}
void swap2(int * a,int * b)//成功//int * a = &a2 int * b = &b2
{
int t = *a;//t = a2;t = *(&a2);
*a = *b;//a2 = b2; *(&a2) = *(&b2);
*b = t;//b2 = t;*(&b2) = t;
}
void swap3(int * a,int * b)//int * a = &a3 int * b = &b3
{
int *t = a;// t ---> at = &a3
a = b;// a ---> ba = &b3
b = t;// b ---> tb = &a3
}
int main()
{
int a1 = 3,a2 = 3,a3 = 3;
int b1 = 5,b2 = 5,b3 = 5;
swap1(a1,b1);
printf("a1 = %d,b1 = %d\n",a1,b1);
swap2(&a2,&b2);
printf("a2 = %d,b2 = %d\n",a2,b2);
swap3(&a3,&b3);
printf("a3 = %d,b3 = %d\n",a3,b3);
return 0;
}
分析:
void test(int *p)
{
printf("data = %d\n",data);// 没有 在 data 的作用域 , 不能通过名字直接访问
*p = 300;
printf("data = %d\n",*p);// 300
}
int main()
{
int data = 250;
printf("data = %d\n",data);
test(&data);
printf("data = %d\n",*p);//
}
--------------------------
int * test()
{
int data = 125;
return &data;
}
int main()
{
int *p = test();
printf("data = %d\n",*p);
}
****注意: 不能返回局部变量的地址
5、数组和指针
数组中每个元素和普通变量一样,也会在内存占个空间,有地址,并且数组元素之间的地址是相邻。
int s[8];
定义了一个数组,名字是s 有8个 int 的元素;
定义一个指针变量,用来保存 s[0] 的地址
int * p = &s[0];
*p <--> s[0]
数组名有些情况下可以当作数组的首地址(第一个元素的地址,&s[0])
假设 &s[0] ---> 0xffff1234567898
&s[1] ---> 0xffff123456789c
&s[0] + 4 == &s[1] ??? 有问题
if(&s[0] + 4 == &s[1])//加了 4 * sizeof(int) 个字节
{
printf("哦嚯,没错\n");
}
else
{
printf("哦嚯,错了\n");
}
指针做加减:
p + i(p 是 一个指针,i 是整数)
***不是简单数值上面的加减,而是加减 i 个 p指针的步长。
指针的步长: 是它(指针)指向的类型那个所占的内存字节数。
如:
char *p;// p 的步长 1
int *q;// q 的步长 4
练习:
int a[10] = {10,9,8,7,6,5,4,3,2,1};
int * p = a;//int * p = &a[0]; p = &a[0]
printf("%d\n",*p);//10 *(&a[0])
printf("%d\n",*p+1);//11(*(&a[0])) + 1
printf("%d\n",*(p+1));//9*(&a[1])
printf("%d\n",*(p+3));//7*(&a[3])
printf("%d\n",*(a+3));//7*(&a[3])
int *q = &a[3];// q = &a[3]
printf("%d\n",*q);//7*(&a[3])
printf("%d\n",*(q+5));//2*(&a[8])
对于数组 xxx a[n];// xxx 代表类型
总结:
(1) 访问数组元素可以用下标法,也可以用指针法
a[i] <----> *(a+i)
0<= i < n 整数
(2) 对于指针p 注意类型
已知 p = &a[i];
*(p+j) <----> *(a+i+j) i + j 不能超过范围
--推导:*(p+j) <----> *(&a[i] + j) <----> *(&a[i+j]) <----> a[i+j]
练习:
分析以下:
输入 hello 输出啥
int main()
{
char s[10];
//scanf("%s",s);// 相当于 &s[0]
scanf("%s",s+2);//_-_-h-e-l-l-o-_ _
printf("%s\n",s+2);//hello s[2] 开始输出 + 2 * sizeof(char)
printf("%s\n",s+3);//ello s[3] 开始输出
return 0;
}
6、数组作为函数参数
int array_sum(int a[],int n)//int a[] <===> int *a = aaa = &aaa[0]
{
int sum = 0;
for(int i = 0;i < n;i++)
{
sum += a[i];
}
return sum;
}
int array_sum(int *a,int n)//int *a = aaa = &aaa[0];int *a; a = aaa;
{
int sum = 0;
for(int i = 0;i < n;i++)
{
sum += a[i];// a[i] <===> aaa[i]//sum += aaa[i];不能这么写
}
return sum;
}
int main()
{
int aaa[10] = {1,3,5,7,8,10,12,2,4,6};
int sum = array_sum(aaa,10);//调用的时候不用 []
printf("该数组的最大值是%d\n",max);
}
//*(a) <----> *(&aaa[i]) <----> *(&aaa[i]) <----> aaa[i]
注意:
int a[] 相当于 int *a ,a不是一个数组,只是退化成一个指针
调用函数的时候,实参是数组首地址
-------------------------------
指针变量 所占内存空间大小是多少?
int * a;
char * b;
float * f;
由它指向的对象类型//错的
指针变量存的是 地址
地址有范围(寻址范围):
4G:
0 ~ 4 * 1024 * 1024 * 1024 - 1
0 ~ 4 * 2^30 - 1
地址范围:0 ~ 2 ^ 32 - 1
32位 是 几个字节 4字节
8G:
0 ~ 8 * 1024 * 1024 * 1024 - 1
0 ~ 8 * 2^30 - 1
0 ~ 2 ^ 33 - 1
这种情况下,指针变量如果还是4字节,无法保存地址,
所以 在 32 位的机器,指针变量占 4字节,64 位 占 8字节
7、作业:
1、写一个函数,求一个字符串的长度
"df\0ghjkl\0\0"
依次比较各个元素,直到匹配到'\0',就结束,没有就继续++
最后返回 个数
2、写一个函数,求两个字符串哪个大
只要A遇到'\0' B还没遇到'\0' B 大
"df\0ghjkl"
"dfghjkl"