一、const修饰指针
const修饰指针 const(常量,不变)
问:被const是否修饰的变量是否有其他方法修改值?
答:可以通过指针的方式绕过a修改其值
int main() { const int a = 10; int* pa = &a;//虽然说对a进行了限制,但是此处绕过了a,去修改值 *pa = 0;//虽然达到了效果,但是操作有点不合理 printf("%d\n", a); return 0; }
问:被const修饰后,变量是否变成了常量?
答:并不是
int main() { const int a = 10;//a不能被修改了,但是a的本质还是变量 a = 20;//const仅仅是语法上做出了限制,习惯上叫常变量 修改-err printf("a = %d\n", a); return 0; }
const修饰指针的时候
1.const可以放在*的左边
2.const可以放在*的右边
1.const放在*的左边
int main() { const int a = 10; int const* p = &a; //限制的是*p //意思是不能通过p来修改p指向的空间的内容 //*p = 0;//err,报错 int b = 20; p = &b;//ok return 0; }
2.const放在*的右边
int main() { const int a = 10; int *const p = &a;//const限制的是p,也就是p变量不能被修改,没办法指向其他变量, //但是*p不受限制,还是可以通过p来修改p所指向的变量 *p = 0;//ok int b = 20; //p = &b;//err,报错 printf("a = %d\n", a); return 0; }
由上述两段代码可以看出:
1.p里面存放的是地址(a的地址)
2.p是变量,有自己的地址
3.*p是p指向的空间
4.const放在*的左边,限制的是*p,意思是不能通过修改指针变量p修改p指向空间的内容。
*p = 20;//err
但是p是不受限制的 p = &b;//ok
5.const放在*的右边限制的是p变量,也就是p变量不能被修改了,没法指向其他变量了。
p = &b;//err
但是*p不受限制,还是可以通过p来修改p所指向的对象的内容。
*p = 20;//ok
二、指针运算
指针的基本运算有三种:
1、指针 +- 整数
2、指针 +- 指针
3、指针的关系运算
2.1指针与整数的运算
在下例中,指针p加一是指向数组的下一个元素
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; //使用指针打印数组的内容 int* p = &arr[0]; int i = 0; for (i = 0; i < 10; i++) { //printf("%d", *p); //p++;//指针加整数 printf("%d\n", *(p + i));//p+i加的是i * sizeof(int) } //p = 00000098240FF5D4 //p+0 = 00000098240FF5D4 //p+1 = 00000098240FF5D8 return 0; }
2.2指针与指针的运算
指针减去指针的得到的是他们之间的元素个数的绝对值
指针-指针运算的前提条件的:两个指针指向同一块空间
int main() { //指针 - 指针 = 地址 - 地址 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; printf("%d\n", &arr[0] - &arr[9]);// |9| char ch[20] = { 0 }; printf("%d\n", &ch[0] - arr[0]);//err return 0; }
2.3指针的关系运算
其实就是地址的比大小
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]);//10 int* p = &arr[0]; //arr是数组名,数组名其实是数组首元素的地址,arr <==>&arr[0] while (p < arr + sz)//使用while循环打印arr的数组 { printf("%d", *p); p++; } return 0; }
三、数组名的理解
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; printf("&arr[0] = %p\n", &arr[0]); printf("arr = %p\n", arr); return 0; }
根据上述代码输出结果,我们发现数组名和数组首元素的地址打印出的结果一模一样,数组名就是数组首元素(第一个元素)的地址。
int main() { int arr[10] = { 0 }; printf("%d\n", sizeof(arr)); printf("%p\n", &arr[0]);//int* printf("%p\n", &arr[0] + 1);//+4 printf("%p\n", arr);//int* printf("%p\n", arr + 1);//+4 //&arr[0]和arr都是首元素的地址,+1就是跳过一个元素 printf("%p\n", &arr); printf("%p\n", &arr+1);//+40 //因为&arr是数组的地址,+1的操作是跳过整个数组的 return 0; }
1.sizeof内部单独放一个数组名的时候,数组名表示的就是整个数组,计算的是整个数组的大小单位是字节。
2.&数组名,数组名表示的是整个数组,取出的是整个数组的地址。
除此之外,遇到的所有数组名都是数组首元素的地址。
3.&arr[0]和arr都是首元素的地址,+1就是跳过一个元素
4.因为&arr是数组的地址,+1的操作是跳过整个数组的
四.使用指针访问数组
1.因为数组在内存中是连续存放的。
2.数组名就是首元素的地址(方便找到起始位置)可以使用指针来访问数组。
代码1(正常写法):
int main() { int arr[10] = { 0 }; int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; int i = 0; for (i = 0; i < sz; i++) { scanf("%d",&arr[i]); } for (i = 0; i < sz; i++) { printf("%d",arr[i]); } return 0; }
代码2(通过指针解引用把值输出):
scanf("%d", p + i);使用scanf函数从用户接收一个整数,并将其存储在指针p加上i所指向的位置。这实际上是向数组中输入数据。
printf("%d ", *(p + i));使用printf函数输出指针p加上i所指向的整数。这实际上是从数组中读取数据并输出。
int main() { int arr[10] = { 0 }; int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; //p <==> arr int i = 0; for (i = 0; i < sz; i++) { scanf("%d", p + i); } for (i = 0; i < sz; i++) { printf("%d ", *(p + i)); } return 0; }
代码3
数组加元素的形式,这里使用arr + i来获取数组第i个元素的地址
int main() { int arr[10] = { 0 }; int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; //p <==> arr int i = 0; for (i = 0; i < sz; i++) { scanf("%d", arr + i); } for (i = 0; i < sz; i++) { printf("%d ", *(arr + i)); } return 0; }
代码4
在C语言中使用i[arr]时,实际上进行了一个指针偏移操作。
1.首先,arr被解析为指向数组首元素的指针。在内存中,数组的元素是连续存储的,而数组名实际上是一个指向数组首元素的指针。因此,通过arr可以找到数组的起始位置。
2.接下来,使用索引i对指针进行偏移。在C语言中,一个指针偏移n个元素就是移动指针到从起始位置开始的第n个元素。因此,通过偏移i个元素,你可以找到数组中第i个元素的位置。
3.arr[i]中的[]是索引运算符,用于访问数组中的元素。它表示将数组名arr解析为指向数组首元素的指针,并使用索引i进行偏移,以访问数组中第i个元素的值。
int main() { int arr[10] = { 0 }; int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; //p <==> arr int i = 0; for (i = 0; i < sz; i++) { scanf("%d", &p[i]); } for (i = 0; i < sz; i++) { printf("%d ", i[arr]); } return 0; }
以上代码说明:
arr[i] == *(arr + i) ==*(i+arr) == i[arr],
&p[i] == &arr[i] == p + i == arr + i,
p[i] == *(p + i)
五、一维数组的本质
引子:我们之前都是在函数外部计算数组的元素个数,把数组传给下一个函数后,函数内部可以求数组元素的个数吗?
数组传参的时候形参是不会创建数组的,实际上传的是首元素的地址,发生了数组降级
void test(int arr[]) //int *arr { int sz = sizeof(arr) / sizeof(arr[0]); printf("%d\n", sz);//? } void Print(int arr[],int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d", arr[i]); //arr[i] == *(arr + i) } } int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]);//获取数组中的元素个数 //test(arr);//这里的数组名就是数组首元素地址 Print(arr,sz); return 0; }
六、传址调用和传值调用
传值调用:传的是变量,传值调用
int Add(int x, int y) { return x + y; } int main() { int a = 10; int b = 20; int ret = Add(a, b); //调用函数 printf("%d\n", ret); return 0; }
传值调用:传的是地址
6.1模拟strlen函数
int my_strlen(const char* s) //const此处保护arr[]中的值,防止被修改 { //size_t = unsigned int size_t count = 0; while (*s != '\0') { count++; s++; } return count; //char* start = s; //while (*s != '\0')// \0的ASCII编码是0 //{ // s++; //} //return s - start; } int main() { //strlen - 求字符串的长度 - 统计的是\0前面出现的字符的个数 char arr[] = "abcdef"; int len = my_strlen(arr);//传的是数组首元素的地址 //数组名是数组首元素的地址 printf("%zd\n", len); return 0; }
6.2为什么有传址和传值两种调用方式
因为有一些问题是不使用指针无法解决的!!!
传值调用函数时,函数的实参传给形参,形参是实参的一份拷贝
形参有自己独立的空间,对实参的修改不会影响实参!!!
void Swap1(int a, int b) { a = a ^ b; b = a ^ b; //b = a ^ b ^ b = a ^ 0 a = a ^ b; } void Swap2(int *pa, int *pb) { int t = 0; t = *pa; *pa = *pb; *pb = t; } int main() { int a = 0; int b = 0; scanf("%d %d", &a, &b); //交换a和b的值 printf("交换前:a = %d b = %d\n", a, b); //Swap1(a, b);//传值调用 此处不发生变化 Swap2(&a, &b);//传址调用 printf("交换后:a = %d b = %d\n", a, b); return 0; } int main() { int a = 10; int* pa = &a;//a和pa有联系 *pa = 20; printf("%d\n", a); return 0; }