C语言作业摘录 第三弹
C语言指针与字符串
指针
虽然一直对指针
这个概念有所耳闻, 但这次才开始深入了解指针.
指针
是一个特殊的变量, 它存储的是一个内存中的位置, 类型代表其位置的大小.
int a;
int *pa;
int a
定义了一个整型的变量a
, int *pa
则定义了一个指针pa
, 它的类型是int
, 也就是指向一个4字节的区域.
*pa = a;
这样pa
指针就指向int a
时在内存中开辟的区域了.
我们讨论一下指针在数组(尤其是char
数组(C风格字符串))的应用.
int arr[8];
这样我们定义一个int
类型数组, 它的长度是8
, 也就是在内存中开辟了连续的4 * 8 = 32
字节空间, arr[i]
代表着一个4字节的空间.
int *p = arr;
这样定义指针p
指向arr
, 也就是开辟的32字节空间的起始点. 由于p
类型为int
, 所以p
代表着从起始点开始的4字节空间.
此时的p
就代表着arr[0]
的值.
printf("%d %d", arr[0], *p);
使用*p
访问p
指代的值, 可以得到和arr[0]
相同的结果.
但如果printf("%d", p);
, 将输出一串数字, 代表的是该数据在内存中的位置, 由于前面未知, 无法正常读取.
这时我们p ++;
, 则p
将指向下一个位置, 由于p
是int
类型指针, 则下一个位置也就是移动4
个字节.
同理, 我们可以让p
一共自增7次, 当p
指向最后一组时, 再让p
自增, 由于内存是非常大的, p
仍然可以指向一个新位置.
但该位置可能并未被定义或初始化, 我们无法确定p
指向位置有什么数据, 写入数据有什么影响.
C风格字符串
对于C风格字符串
, 也就是char数组
, 我们可以用指针来方便的传参, 获取某位. 同时string.h
标准库中的各个函数也都是接受/返回指针作为参数的.
char str[100] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
char *p = str;
char
恰好只占1
个字节, 所以我们画图表示, 也方便很多.
我们可以使用p
访问到'L'
字符.
获取长度
字符串长度指的是从第一个字符到结束字符\0
的长度, 而非数组长度(显然数组长度是大于实际长度的). 我们可以通过循环的方式来计算长度.
int len = 0;
char *p = str;
while (*p != '\0') {
len ++;
p ++;
}
p
自增:
p
一直自增到读取到结束符\0
:
复制字符串
当我们定义好字符串, 可以直接定义新的指针指向该字符串, 创建浅拷贝, 然而这样几个拷贝直接会互相影响, 所以我们要创建新的空字符串, 之后将字符一个一个输入到新字符串中.
char a[100] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
char b[100];
char *pa = a;
char *pb = b;
while (*pa != '\0') {
*pb = *pa;
pa ++;
pb ++;
}
我们定义一个指向a
的指针pa
, 之后用相同的方法遍历数组, 将每个值复制到指向b
的指针pb
中, 即写入b
数组的对应位中.
字符串比较
首先比较字符串长度, 若长度不相同, 则字符串不可能相等, 先比较长度可以省去部分算力.
而长度比较函数我们刚才已经写了出来, 不过现在没有现成的函数可以用, 我们还是直接用指针遍历字符串, 按位比较.
char a[100] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
char b[100] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
char *pa = a;
char *pb = b;
while (*pa != '\0') {
if (*pa != *pb) {
printf("Not Equal");
return 0;
}
pa ++;
pb ++;
}
printf("Equal");
原理和上述字符串复制基本相同.
同理, 我们可以使用指针实现字符串查找某字符, 字符串查找字符串等.
string.h
标准库函数
上式操作已经被封装到string.h
标准库中, 我们可以直接使用标准库的函数进行操作.
strlen(str)
, 返回字符串str
的长度.strcpy(a, b)
, 将b
字符串复制到a
字符串中.strcat(a, b)
, 将b
字符串追加到a
字符串后面.strcmp(a, b)
, 比较a
,b
字符串的字典顺序.strchr(a, c)
, 在字符串a
中查找字符c
.strstr(a, b)
, 在字符串a
中查找子字符串c
.
上述函数中传入的字符串, 实际上都是指针, 表示的从指针位置开始到结束符\0
的一段字符串.
示例:
#include <stdio.h>
#include <string.h>
int main () {
char a[100] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
char b[200] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
printf("%d\n", strlen(a)); // 56
char c[100];
strcpy(c, a);
printf("%s\n", c); // Lorem ...
strcat(b, a);
printf("%s\n", b); // Lorem ... * 2
printf("%d\n", strcmp("abc123", "abc124")); // "abc123" < "abc124", return -1
return 0;
}
复杂操作接下来例题中再做讲解.
例题
统计字符串中英文字符, 数字字符和其他字符的数量
直接上代码, 前后略.
int alpha = 0, digit = 0, other = 0;
char *p = str;
while (*p != '\0') {
if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')) {
alpha ++;
} else if (*p >= '0' && *p <= '9') {
digit ++;
} else {
other ++;
}
p ++;
}
使用ASCII
字符的顺序来分类字符.
删除字符串中的指定字符
使用write
和read
两个指针来读写.
void delChar (char *p, char c) {
char *write = p;
char *read = p;
while (*read) {
if (*read != c) {
*write = *read;
write ++;
}
read ++;
}
*write = '\0';
}
read
指针用于读取内容, 正常情况下, 它们同时移动.
当read
指针读取到需要删除的字符时, write
则停止移动, 这时read
读取到内容, write
会直接覆盖掉被删去的字符和它之后的位置.
同步读写:
读取到需要删除的字符(这里以空格为例):
这时仅read ++
, write
保持不动:
之后write
写入*read
:
接下来read
和write
继续前进, 覆盖字符串:
这时read
再次读取到空格, write
再停止一步, 之后同步:
此时write
指针再次等待.
最终:
这时read
再次移动, 得到结束符\0
, 这时write
再次移动, 写入结束符\0
, 这时字符串变为:
现在字符串虽然后面扔有残留字符, 但由于倒数第4位处位结束符\0
, 则说明该字符串到此处结束, 后面有无字符无所谓, 不会参与运算.
字符串循环移位
对于字符串
str
, 输入数字n
, 将字符串里面的字符循环向左移动n
位.
例如, 输入123456789 3
, 则输出456789123
.
首先, 我们先处理n
, 对于一个长度为9
的字符串, 若n = 9
, 则无需处理; 若n = 10
, 则相当于n = 1
.
故先使n
对字符串长度取模.
之后拆分字符串, 将后半部分先复制到新字符串中, 再追加前半部分, 返回(或使覆盖)新字符串.
代码如下:
void cyclicShift(char *ps,int n){
int len = strlen(ps);
n %= len;
char newStr[N];
ps += n;
strcpy(newStr, ps);
*ps = '\0';
ps -= n;
strcat(newStr, ps);
strcpy(ps, newStr);
}
第一步取模部分无需讲解.
第二步创建字符串并复制后半部分, 这里先使指针移动n
位, 从现在开始就是字符串后半部分了, 复制字符串到newStr
.
第三步追加前半部分, 这里需要将指针移动回去, 但是如果直接追加, 无法控制长度, 将一直追加到最后面, 变成456789123456789
.
所以这里先将现在的位置写入终止符\0
, 再将指针移动回去, 这样就只会追加前半部分了.
单词统计
统计一行英文中单词的个数. 注意, 由于书写问题, 单词之间可能有多个空格, 标点和单词直接空格未知, 字符串前后可以有若干空格.
例如, 输入This is my favorite movie.
, 输出5
.
我们思考如何区分单词. 显然, 单词通过空格
, 标点
分割, 但上述说明中发现这些数目都不固定, 难以区分.
我们从前往后读字符串: 当由其他字符进入字母时, 这是一个单词的开始; 当从字母变成其他字符时(空格, 标点), 这是一个单词的结束; 而从字母到字母和字符到字符都说明不了什么.
所以我们只要统计字母变成其他字符的情况即可.
int wordCounter (char *ps) {
int count = 0;
int isAlpha = 0;
while (*p != '\0') {
if ((*ps >= 'a' && *ps <= 'z') || (*ps >= 'A' && *ps <= 'Z')) {
isAlpha = 1;
} else {
if (isAlpha) count ++;
isAlpha = 0;
}
ps ++;
}
if (isWord == 1) count ++;
return count;
}
这里使用isAlpha
存储是否为字母. 当读取到字母时, 令isAlpha = 1
, 标记为字母; 当读取到非字母时, 若isAlpha
, 即上一个标记的是字母, 则计数一个单词, 同时令isAlpha = 0
, 标记为非字母;
循环结束后, 还要再次进行一次判断, 防止This is my favorite movie
这样结尾没有字符的漏判.