指针
0.展示PTA总分
1.本章学习总结
1.1 指针定义、指针相关运算、指针做函数参数
1.指针定义及赋值
int a;
int * p=&a;
&:取地址:
*:取内容:
均是右结合
2. 指针运算
1.加法
类型 | 第一步 | 第二步 |
---|---|---|
* p++ | * p | p++ |
(*p)++ | *p | (*p)+1 |
*++p | ++p | *(++p) |
p++;是将指针p所指的位置后移一个所指的变量单元 | ||
![]() |
||
2.减法 | ||
![]() |
||
得到的是相差的元素个数 | ||
当“=”的左操作数是*p时,改变的是p所指向的地址存放的数据; | ||
当“=”的左操作数是p时,改变的是p所指向的地址。 |
3.做函数参数
基本类型 的变量做函数参数 |
指针类型 的变量做函数参数 |
---|---|
Call by Value——Passing arguments by value | Simulating Call by reference——Passing arguments by reference |
实参变量的值 ->形参(parameter) |
实参变量的地址 ->指针 形参(pointer parameter) |
在被调函数中不能改变实参的值 | 在被调函数中可以改变实参的值 |
![]() |
|
swap1 | |
![]() |
|
swap2 | |
![]() |
|
注意事项 | |
对指针进行初始化或赋值的实质是将一个地址或同类型(或相兼容的类型)的指针赋给它,而无论这个地址是怎么取得的。要注意的是:对于一个不确定要指向何种类型的指针,在定义它之后最好把它初始化为NULL,并在使用这个指针时对它进行检验,防止是野指针。另外,为程序中不论什么新创建的变量提供一个合法的初始值是一个好习惯,它能够避免一些不必要的麻烦。 |
1.2 字符指针
定义赋值
字符串函数
序号 | 函数 | 原型 | 功能 |
---|---|---|---|
1 | strlen | size_t strlen(const char *str) | 计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。 |
2 | strcat | char *strcat(char *dest, const char *src) | strncmp |
3 | strncat | char *strncat(char *dest, const char *src, size_t n) | 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。 |
4 | strcpy | char *strcpy(char *dest, const char *src) | 把 src 所指向的字符串复制到 dest。 |
5 | strncpy | char *strncpy(char *dest, const char *src, size_t n) | 把 src 所指向的字符串复制到 dest,最多复制 n 个字符 |
6 | strcmp | int strcmp(const char *str1, const char *str2) | 把 str1 所指向的字符串和 str2 所指向的字符串进行比较。 |
7 | strncmp | int strncmp(const char *str1, const char *str2, size_t n) | 把 str1 和 str2 进行比较,最多比较前 n 个字节。 |
8 | strcoll | int strcoll(const char *str1, const char *str2) | 把 str1 和 str2 进行比较,结果取决于 LC_COLLATE 的位置设置。 |
9 | strstr | char *strstr(const char *haystack, const char *needle) | 在字符串 haystack 中查找第一次出现字符串 needle(不包含空结束字符)的位置。 |
10 | strrchr | char *strrchr(const char *str, int c) | 在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置。 |
11 | strchr | char *strchr(const char *str, int c) | 在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。 |
12 | strcspn | size_t strcspn(const char *str1, const char *str2) | 检索字符串 str1 开头连续有几个字符都不含字符串 str2 中的字符。 |
13 | strpbrk | char *strpbrk(const char *str1, const char *str2) | 检索字符串 str1 中第一个匹配字符串 str2 中字符的字符,不包含空结束字符。也就是说,依次检验字符串 str1 中的字符,当被检验字符在字符串 str2 中也包含时,则停止检验,并返回该字符位置。 |
14 | strspn | size_t strspn(const char *str1, const char *str2) | 检索字符串 str1 中第一个不在字符串 str2 中出现的字符下标。 |
15 | strxfrm | size_t strxfrm(char *dest, const char *src, size_t n) | 根据程序当前的区域选项中的 LC_COLLATE 来转换字符串 src 的前 n 个字符,并把它们放置在字符串 dest 中。 |
16 | strerror | char *strerror(int errnum) | 从内部数组中搜索错误号 errnum,并返回一个指向错误消息字符串的指针。 |
以上string.h头文件里的函数调用简单方便,有时候也要注意时间复杂度的问题,不要频繁调用。 |
1.3 指针做函数返回值
形如:
char* getmonth(int n)
{
char *a[20] = { "January", "February", "March", "April", "May", "June", "July", "August",
"September", "October", "November", "December" };
if (n >= 1 && n <= 12) return a[n-1];//返回指针
else return NULL;//没有要返回空
}
注意
返回值必须是指针,要么就是NULL
不要使用局部变量的地址作为函数返回值,如样例所示。如果不是指针数组,结果就不同
使用栈内存返回指针是明显错误的,因为栈内存将在调用结束后自动释放,从而主函数使用该地址空间将很危险。
使用堆内存返回指针是正确的,但是注意可能产生内存泄露问题,在使用完毕后主函数中释放该段内存。
1.4 动态内存分配
先讲一下堆区和栈区
C语言的内存模型分为5个区:栈区、堆区、静态区、常量区、代码区。每个区存储的内容如下:
1、栈区:存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了,其操作方式类似于数据结构中的栈。栈内存分配运算内置于CPU的指令集,效率很高,但是分配的内存量有限,比如iOS中栈区的大小是2M。
2、堆区:就是通过new、malloc、realloc分配的内存块,编译器不会负责它们的释放工作,需要用程序区释放。分配方式类似于数据结构中的链表。“内存泄漏”通常说的就是堆区。
3、静态区:全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后,由系统释放。
4、常量区:常量存储在这里,不允许修改。
5、代码区:顾名思义,存放代码。
栈区:由系统自动分配,速度较快。但程序员是无法控制的,空间小。(只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。)
堆区:是由程序员自己调节分配的内存,一般速度比较慢,空间大,而且容易产生内存碎片,不过用起来最方便。
malloc:void * malloc(size_t size)
1.malloc()函数会向堆中申请一片连续的可用内存空间
2.若申请成功 ,,返回指向这片内存空间的指针 ,若失败 ,则会返回NULL, 所以我们在用malloc()函数开辟动态内存之后, 一定要判断函数返回值是否为NULL.
3.返回值的类型为void型, malloc()函数并不知道连续开辟的size个字节是存储什么类型数据的 ,所以需要我们自行决定 ,方法是在malloc()前加强制转 ,转化成我们所需类型 ,如: (int)malloc(sizeof(int)*n).
如果size为0, 此行为是未定义的, 会发生未知错误, 取决于编译器
calloc:void * calloc(size_t num,size_t size)
1.calloc()函数功能是动态分配num个大小(字节长度)为size的内存空间 .
2.若申请成功 ,,返回指向这片内存空间的指针 ,若失败 ,则会返回NULL, 所以我们在用calloc()函数开辟动态内存之后, 一定要判断函数返回值是否为NULL.
3.返回值的类型为void型, calloc()函数虽然分配num个size大小的内存空间 ,但还是不知道存储的什么类型数据 ,所以需要我们自行决定 ,方法是在calloc()前加强制转 ,转化成我们所需类型 ,如: (int)calloc(num, sizeof(int)).
与malloc()函数的区别只在于, calloc()函数会在返回地址之前将所申请的内存空间中的每个字节都初始化为0.
free:void free(void* ptr)
对于申请空间要有始有终,用了之后要换回去,再借不难。
在堆中申请的内存空间不会像在栈中存储的局部变量一样 ,函数调用完会自动释放内存 , 如果我们不手动释放, 直到程序运行结束才会释放, 这样就可能会造成内存泄漏, 即堆中这片内存中的数据已经不再使用, 但它一直占着这片空间, 所以当我们申请的动态内存不再使用时 ,一定要及时释放 。
不能重复释放同一块内存!!!
编程技巧:
动态申请内存时,写完申请后就在下面将释放写上去,不容易忘记。
1.5 指针数组及其应用
int* p[4];
首先:
指针数组:指针的数组,表示一个数组,并且数组的每一个元素都是指针类型。
数组指针:数组的指针,表示一个指针,并且是指向数组的指针。
其次:
指针数组可以用来表示多个字符串,在堆区申请内存,不用担心内存不够的问题
指针数组元素可以相互赋值,更灵活,不用在调用函数
最后:
指针数组经常用于字符串处理过程中,利用指针数组方法编写程序使得效率更高
注意事项
指针数组作为函数形参时,一定不要移动首地址,用另一个指针代替,否则,最后会不知道指针指向了哪里,这样很危险
1.6 二级指针
指针是一种变量,它也有自己的地址,所以它本身也是可用指针指向的对象。我们能够将指针的地址存放在还有一个指针中
int * * p;
int * q;
p=&q;
指向指针的指针
一个 * 是表示地址
两个 * * 表示值
1.7 行指针、列指针
行指针:二级指针
1.形如 int(*p)[n]
指向有n个元素的一维数组
p+i=a+i;
*(p+i)=*(a+i)=a[i]
2.二维数组与指针
int a[3][4];
int (*p)[4];
p=a;
(*p)[0]=a[0][0];
(*(p+1))[0]=a[1][0];
*(*(p+i)+j)=a[i][j];
列指针:一级指针
int a[3][4];
int * p;
p=a;
* (p+1)=a[0][1];
//移向下一个元素
注意区分行指针与列指针
行指针:p首先指向第0行,然后p+i定位到第i行,然后p+i进行解引用(*(p+i))把行地址转化为列地址,在得到第i行第0列地址后在加j得到第i行第j列地址,在进行解引用得到a[i][j]
列指针:p直接指向了第0行第0列,找出a[i][j]相对于a[0][0]的偏移量,i * n+j
2.PTA实验作业
2.16-10 填充矩阵
2.1.1伪代码
本题目是一个行指针运算
先定义一个行指针q用来存放p的首地址
先进行下三角赋值
for i->0 to n
for j->0 to n
将(*p)[j]赋值为2
end for
p++ 移向下一行
end for
p=q 将p再次指向首地址
下面进行上三角赋值
for i->0 to n
for j->0 to n
将(*p)[j]赋值为1;
end for
p++ 移向下一行
end for
p=q 将p再次指向首地址
最后进行对角线赋值
for i->0 to n
for j->0 to n
将(*p)[j]赋值为1
end for
p++ 移向下一行
end for
2.1.2代码截图
2.1.3同学互学
- 优点:代码比我的简洁明了,分三个层次,更简洁,而我的重复代码太多,可读性差。
- 缺点:将指针转化为数组,没有用指针去做,缺少了点注释
2.2合并两个有序数组
2.2.1伪代码
void merge(int* a, int m, int* b, int n) 合并a和b到a
{
当a与b数值都有元素时
用数组s存放a中数据
for i=0->m
s[i]=a[i]
end for
for i=0->m+n
比较s[j]与b[k]的大小
并将小的先填入a[i]中
if 有一个数组元素没有了就停止比较
end for
将剩余的元素写入a数组中
if a数组中没有元素即m为0时
直接就将b数组存入a中
}
2.2.2代码截图
2.2.3和同学对比
- 优点:从最后开始往前,避免多开一个数组,而且思路也更清晰,也不用判断是否有一个数组是空的,还有注释可读性高,而我的则显得冗长,代码较多,没有优化
- 缺点:。。。还没看出来缺点,倒是觉得自己的代码很low
2.37-4 说反话-加强版
2.3.1伪代码
定义一个字符数组line用来读取数组
一个指针数组
用正则表达式读取字符
for i=0->len//字符串长度
if line[i]不为' ' 并且word为0
l[++top]=&line[i]//记录单词首字母
word=1
else word=0
end for
line[len]=' '
line[++len]='\0'//结束标志
for i=top->1
char *p=l[i]//用一个字符指针遍历l数组
if *p!=' '
输出字符
p++
if i!=1
就要输出一个空格
end for
2.3.2代码截图
2.3.3对比视频
学习点
- 指针 逆序遍历,很清楚,p!=startP
- 输出 printf("%.*s",len,str),用这个输出使得程序实现更容易,
- 回车问题 巧妙地用一个while (endP&&endP!='\n')将回车去掉
- 找单词首地址 p != ' '&&(p-1)==' '
- 相比之下,自己的代码过于复杂,难以看懂