c语言笔记(鹏哥)上课板书+课件汇总(深入指针3)
联系:数组名是首元素地址,把它交给指针变量,利用指针变量来访问数组,指针变量的大小是4字节(x86)或8字节(x64),数组的大小依据数组元素类型和数组的元素个数而定的。实际上前两对地址是int* 类型的(代表的是数组首元素的地址),后一对的是数组的地址,这个类型鹏哥后续讲,先留一个疑点。str1和str2是两个不同的数组,第一个if比较的是两个字符串数组的首地址,不同数组在内存中的分布位置不同。
深入指针3
一、数组名的理解

1. 验证一下数组名 = 数组首地址(注意此处编译器环境是x86环境)
#include <stdio.h>
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;
运行结果是:
2. 验证一下sizeof(数组名)算的是整个数组的大小(长度)
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("arr[0] = % d\n", sizeof(arr[0]));
printf("arr = %d", sizeof(arr));
return 0;
}

3.验证一下&arr取的是整个数组的地址:(数组的地址就是数组的首地址)
#include<stdio.h>
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);
printf("&arr = %p\n", &arr);
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[0]+1 = %p\n", &arr[0]+1);
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr = %p\n", &arr);
printf("&arr+1 = %p\n", &arr+1);
return 0;
}

- 第一对跳了4个字节(一个整形元素大小)
- 第二对跳了4字节
- 第三队跳了B20-AF8=28(16进制)
28=2 * 16+ 8 * 1=40个字节(整个数组)
实际上前两对地址是int* 类型的(代表的是数组首元素的地址),后一对的是数组的地址,这个类型鹏哥后续讲,先留一个疑点。
三、使用指针访问数组
- 使用下标访问数组
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
- 使用指针(实现数组元素的输入,输出)

先找到指针的首地址,再通过指针加减运算以及解引用访问每一个元素
- 两种输入方式二选一
- 四种输入方式四选一
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
//输入
for (i = 0; i < sz; i++)
{
//scanf("%d ",p+i);//p+i代表的是第i个元素的地址
scanf("%d", &arr[i]);
}
//输出
for (i = 0; i < sz; i++)
{
//printf("%d ",*(p+i));
//printf("%d ", *(arr + i));
//printf("%d ", arr[i]);
printf("%d ", i[arr]);
}
return 0;
}
- *(arr+i) = arr[ i ]
- *(arr+i)= * (i+arr) //满足交换律
- *(i+arr) = i[arr]
- *(arr+i) = arr[ i ] = * (i+arr)= i[arr]
注:arr = p
写法可以有多种
- 在这个过程中编译器本质上是把arr[ i ]解读成(arr+i)由于要解读成元素,在对其进行解引用变成*(arr+i)
- 一般的数组元素写法是把数组名写在前,方括号内部写下标,arr[ i ],交换写也是可以的,所以可看出来[ ]其实就是一个操作符,就像是+,a+b和b+a,交换顺序是完全没问题的,编译器本质上进行了arr[ i ]=*(arr+i),i [ arr ] = *(i+arr)操作

联系:数组名是首元素地址,把它交给指针变量,利用指针变量来访问数组,指针变量的大小是4字节(x86)或8字节(x64),数组的大小依据数组元素类型和数组的元素个数而定的。
四、一维数组传参的本质
一维数组传参的本质就是传递数组首元素的地址
传参的时候不加首地址(后续会将取地址数组名,有特殊需要才加取地址,一般不用)
#include<stdio.h>
void test_1(int arr[10])//void test_1(int* arr)
{
int sz2 = sizeof(arr) / sizeof(arr[0]);
printf("sz2 = %d", sz2);
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz1 = sizeof(arr) / sizeof(arr[0]);
printf("sz1 = %d\n", sz1);
test_1(arr);
return 0;
}
观察运行结果:
- 分析结果为什么是sz2 = 1, sizeof(arr[0])必然是4,那么sizeof(arr) 也应该是4,正常应该是40,说明这里接收到的数组(传进来的arr有问题)不是数组,而是指针(在x86环境下是4字节,x64环境下是8字节),可以改成x64环境测试一下结果会是2
- 传递给函数的是数组名arr(数组元素首地址),那么接收的时候也应该用指针变量去接收,所以函数声明写成void test_1(int* arr),写成数组是为了方便接受语法,传进去数组用数组接收。
- 但其实本质上是传了指针,接收用数组(理解本质上不是创建了形参数组,而是创建了临时指针变量就可以,所以写数组的时候可以不写元素个数,或元素个数随便写都可以,直接写void test_1(int arr[ ]),用指针也是可以的。
- 所以形参即使写成数组的形式,本质上也是一个指针变量
当我们想用数组的个数的时候直接将其传过去就可以
比如想利用函数和指针访问数组所有元素:需要传入首地址和元素个数
#include<stdio.h>
void test_1(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);//即使没有把数传进来,
//依然可以通过下标找到数组元素
//原因是编译器会将其翻译成*(arr+i)在内存中找到该地址处
//解引用出该地址中的元素
/*printf("%d ", i[arr]);
printf("%d ", *(arr+i));
printf("%d ",*(i+arr));*/ //任选4其1
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz1 = sizeof(arr) / sizeof(arr[0]);
printf("sz1 = %d\n", sz1);
test_1(arr,sz1);
return 0;
}
总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
五、冒泡排序和选择排序
一组数据如:
2 3 1 4 5 6 7 9 8 10(乱序)
1 2 3 4 5 6 7 8 9 10(升序)
9 8 7 6 5 4 3 2 1 0(逆序)
将数据按照升序排列
算法实现:
- 先确定趟数,数组中含有n个元素,就会有n-1趟
- 内部数组元素下标为 j 和 j+1 的进行比较
- 在一趟内部进行元素对的两两比较,一共要进行n-1-i对比较
代码实现如下:
void input(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
scanf("%d", &arr[i]);
}
}
void bubble_sort(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)//确定趟数
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)//内部两两比较,确定对数
{
if (arr[j] > arr[j + 1])//比较交换
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
//打印
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
input(arr, sz);//输入
bubble_sort(arr, sz);
return 0;
}
冒泡排序的优化:
- 如果把每一趟都算上的话,需要比较9+8+7+6+5+4+3+2+1=45对元素
- 如果遇见1 2 3 4 5 6 7 8 9 10这类原本就是升序的数组,只需要进行一趟两两比较,发现根本没有换位置的元素,就可以停止了,不需要再进行下一个元素两两比较了,时间上大大缩短了
- 实际上这种优化缩减了原本就是升序或者近似于升序(1 0 2 3 4 5 6 7 8 9)的比较次数
void input(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
scanf("%d", &arr[i]);
}
}
void bubble_sort(int* arr, int sz)
{
int count = 0;//count代表比较的元素对数
int i = 0;
//定义flag变量表示如某一趟比较中没有交换的
//说明顺序就是升序了直接停止比较
//可以把次数限制在刚刚好排成升序的时候
for (i = 0; i < sz - 1; i++)//确定趟数
{
int flag = 1;
int j = 0;
for (j = 0; j < sz - 1 - i; j++)//每一趟内部两两比较,确定对数
{
count++;
if (arr[j] > arr[j + 1])//比较交换
{
flag = 0;
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
if (flag == 1)//在一趟交换后,根本没发生交换(前面元素大于后面元素)
//说明已经是升序,则直接停止循环
{
break;
}
}
//打印
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\ncount = %d",count);
}
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
input(arr, sz);//输入
bubble_sort(arr, sz);
return 0;
}
选择排序:
过程:

程序源码:
void select_sort(int* arr,int count)
{
int i = 0;
int j = 0;
for (i = 0; i < count - 1; i++)//确定需要排序的轮数
{
int p = i;//定义最小元素下标
for (j = i+1; j < count; j++)//确定当前最小元素下标
{
if (arr[j] < arr[p])
{
p = j;
}
}
if (p != i)//将当前位置换成当前最小元素
{
int tmp = arr[i];
arr[i] = arr[p];
arr[p] = tmp;
}
}
}
int main()
{
int arr[8] = { 6,4,7,3,2,1,5,8 };
int sz = sizeof(arr)/sizeof(arr[0]);
selest_sort(arr, sz);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
六、二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在二级指针中

- 可以将a理解为一个抽屉,他有一个钥匙1
- pa也是一个抽屉,装着钥匙1,他也有一个钥匙2
- ppa也是一个抽屉,装着钥匙2
- 可以由*ppa打开抽屉ppa得到钥匙2,可以找到pa
- 可以由*pa打开抽屉pa得到钥匙1,找到a
七、指针数组:
类似与:好孩子(好—修饰词,孩子是主语)
指针数组:指针是修饰词,数组是主语

八、这里举例:(指针数组格式书写)
指针数组int*parr[3],parr[3]这个数组存放了3个指针变量,数组名又是指针(数组首元素的地址),所以可以作文指针数组的元素。
int arr1【5】 = {1,2,3,4,5};
int arr2 【5】= {2,3,4,5.6};
int arr3 【5】= {3,4,5,6,7};
int * parr[3]={arr1,arr2,arr3};
再举一例:用指针数组存放地址(指针)
int a = 100;
int b = 90;
char ch = ‘w’;
char * cp[3]={&a,&b,&ch};
九、指针数组模拟⼆维数组

int main()
{
int arr1[5] = {1,2,3,4,5};
int arr2 [5] = {2,3,4,5,6};
int arr3 [5] = {3,4,5,6,7};
int* parr[3] = { arr1,arr2,arr3 };
int i = 0;
for (i = 0; i < 3; i++)//行号
{
int j = 0;
for (j = 0; j < 5; j++)//列号
{
printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}
parr[ i ][ j ]的解读:
- 通过数组名parr找到指针数组的首地址,编译器将其先解读parr[i]成 *(parr+i)找到arri的地址,解引用找到了arr[i]这个数组,
- 在解读parr[i][j]为 * ( *(parr+i)+j)编译器找到了arri[j]中各个元素的地址
- parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数组中的元素。
十、字符指针变量
使用方法:
int main()
{
char ch = 'w';
char* cp = &ch;
*cp = 'h';//可以修改
printf("%c", *cp);
return 0;
}
将一串常量字符串存储在字符指针中:
int main()
{
char* ch = "abcdef";
printf("%p", ch);
return 0;
}

- 可以看出字符型指针并没有把字符串存进指针ch里面,而是存进去一个地址,这个地址是常量字符串的首元素地址。
将一串字符存放在字符数组中,将一串字符存进字符指针中:
int main()
{
char arr[10] = "abcdrf";
char* p = arr;
*p = 'b';
printf("%c", arr[0]);
char* ch = "abcdef";
*ch = 'c';
printf("%c", *ch);//未显示,不可修改
return 0;
}

- 可以修改字符数组的字符
十一、不可以修改常量字符串的值

那么我们进行char* ch = “abcdef”;的时候会有一个危险性,本来是不可以修改常量字符串的值,但是通过*ch又找到首字符a,*ch='w’对其修改,逻辑上又没问题,所以要避免这种危险性,加上const即可
正确使用字符指针存储字符串:const char* ch="abcdef"
十二、《剑指offer》中收录了⼀道和字符串相关的笔试题,我们⼀起来学习⼀下
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";
const char *str4 = "hello bit.";
if(str1 ==str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3 ==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}

str1和str2是两个不同的数组,第一个if比较的是两个字符串数组的首地址,不同数组在内存中的分布位置不同
str3和str4是两个字符指针指向同一个常量字符串,(首元素的地址),常量字符串不可修改,没有必要用两个指针去指向它,内容相同的常量字符串只需要保存一份就够了。
这⾥str3和str4指向的是⼀个同⼀个常量字符串。
C/C++会把常量字符串存储到单独的⼀个内存区域,只读数据区
当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
使用%s打印字符串的时候,只需要提供首字符的地址就可以
int main()
{
const char * p = "abcdef";
printf("%c\n", *p);
printf("%s\n", p);//使用%s打印字符串的时候,只需要提供首字符的地址就行
return 0;
}
p指向只读数据区的常量字符串首地址,所以可以直接把常量字符串打印出来
十三、数组指针

使用方法:
int main()
{
int arr[5] = { 1,2,3,4,5};
int* p[5] = { arr };//指针数组,存放指针的数组
//指针类型是int*,等号两边都是int*
int(*pa)[5] = &arr;//数组指针,指向数组的指针,存放数组的地址
//类型是int(*)[5],等号两边都是int(*)[5]
//arr类型是 int*-----+1跳过4个字节
//&arr[0]类型是 int*-----+1跳过4字节
//&arr类型是 int(*)[n]--+1跳过40个字节
}
注:[ ]的优先级大于*
* 对等号左边指针解引用相当于对等号右边的解引用
int main()
{
char* ch[5];//字符型指针数组,存放字符型指针的数组
char* (*cp)[3] = &ch;//字符型数组指针,指向字符数组的指针,数组中每个元素类型是char*
return 0;
}
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)