这是关于 C 语言字符串操作 和 二分查找基础概念 的笔记,以下是核心知识点总结:
一、二分查找基础
- 核心公式:
int mid = (begin + end) / 2
(用首尾索引的中间值缩小查找范围,适用于有序序列查找)
代码:
int main(void) //利用二分法查找数。使用二分法需要对数组先进行排序。
{
int a[]={1,2,3,4,5,6,7,8,9,10};
int len=sizeof(a)/sizeof(a[0]);
int begin=0,end=len-1;
int n;
scanf("%d",&n);
int middle;
while(begin<=end)
{
middle=(begin+end)/2;
if(a[middle]>n)
{
end=middle-1;
}
else if(a[middle]<n)
{
begin=middle+1;
}
else
{
break;
}
}
if(begin<=end)
{
printf("found!下标是:%d\n",middle);
}
else
{
printf("NOFOUND!\n");
}
return 0;
}
这段代码实现了二分法查找算法,其核心思想可以概括为以下几点:
-
前提条件:二分法查找的前提是数组必须已经排好序(这里是升序排列的数组
a
) -
核心思路:通过不断将查找范围减半来快速定位目标值
- 首先确定查找范围的起始点
begin
(初始为0)和结束点end
(初始为数组最后一个元素的索引) - 计算中间位置
middle
,将目标值n
与中间位置的元素a[middle]
比较 - 如果
a[middle]
大于n
,说明目标值在左半部分,调整end
为middle-1
- 如果
a[middle]
小于n
,说明目标值在右半部分,调整begin
为middle+1
- 如果相等,则找到目标值,跳出循环
- 首先确定查找范围的起始点
-
终止条件:
- 找到目标值时,通过
break
跳出循环 - 当
begin
大于end
时,说明整个数组已查找完毕但未找到目标值
- 找到目标值时,通过
-
时间复杂度优势:二分法查找的时间复杂度为O(log₂n),相比顺序查找的O(n),在数据量较大时效率显著提高
这种方法的关键在于每次比较后都能排除一半的查找范围,从而快速逼近目标值或确定目标值不存在。
二、字符串(字符数组)基础
-
字符串结束标志:
'\0'
(ASCII 值为 0,是 C 语言识别字符串结束的标记)- 示例:
char st[100] = {'a', 'b', 'c'};
未显式赋值的部分会自动填充'\0'
,但需注意手动初始化时要预留'\0'
位置。char s[100] = "Hello";
编译器自动在末尾补'\0'
,实际占用 6 个字符(H e l l o \0
)。
- 示例:
-
字符串长度与存储:
sizeof(s)
:计算数组总大小(如char s[100]
结果为 100,包含未使用空间)。strlen(s)
:计算有效字符长度(从首地址到'\0'
的字符数,不包含'\0'
)。
三、字符串输入函数
gets(s)
:
读取一行字符串(包含空格),但不安全(可能因输入过长导致数组越界),现代 C 已弃用。scanf("%s", s)
:
读取字符串,遇到空格/换行停止,无法读取带空格的内容。fgets(s, sizeof(s), stdin)
:
更安全的输入方式,sizeof(s)
限制读取长度(防止越界),会保留换行符(需手动处理时可s[strcspn(s, "\n")] = '\0';
清除)。将输入的字符转换为全部大写:
int main(void)
{
int i=0;
char s[10]="Hello";
// scanf("%s",&s);
// fgets(s,sizeof(s),stdin);
while(s[i]!='\0')
{
if(s[i]>='a'&&s[i]<='z')
{
s[i]-=32;
}
++i;
}
puts(s);
return 0;
}
这段代码的功能是将字符串中的所有小写字母转换为大写字母并输出。
代码的核心逻辑如下:
- 定义了一个字符数组
s
并初始化为"Hello" - 使用
while
循环遍历字符串,直到遇到字符串结束符'\0'
- 在循环中,判断当前字符是否为小写字母(
a-z
之间) - 如果是小写字母,则通过减去32(ASCII码差值)将其转换为对应的大写字母
- 循环结束后,使用
puts
函数输出转换后的字符串
由于初始字符串是"Hello",其中只有字母’l’是小写,所以程序会将其转换为大写,最终输出"HELLO"。
代码中还注释了两种输入方式:
scanf("%s",&s);
:可以从标准输入读取字符串fgets(s,sizeof(s),stdin);
:也可以从标准输入读取字符串,相对更安全
如果启用其中一种输入方式,程序就可以处理用户输入的任意字符串,将其中的小写字母全部转换为大写字母后输出。
三、输入函数对比(解决实际场景)
函数 | 特点 | 适用场景 | 缺点 |
---|---|---|---|
gets | 读一行(含空格),到换行停止 | 旧代码兼容 | 不安全(易越界) |
scanf | 读字符串,遇空格/换行/制表符停止 | 读简单无空格内容 | 无法读带空格的字符串 |
fgets | 读一行(含空格),可限制长度 | 安全读用户输入 | 会把换行符 \n 存进去 |
fgets
处理换行符:
如果用 fgets(s, sizeof(s), stdin)
,输入 abc
后,s
里实际是 abc\n\0
,想去掉 \n
可这样:
s[strcspn(s, "\n")] = '\0'; // strcspn 找 "\n" 位置,替换成 '\0'
统计用户输入字符串中不同类型字符的数量:
int main(void)
{
char s[100];
fgets(s,sizeof(s),stdin);
int i=0;
int counter1=0;
int counter2=0;
int counter3=0;
int counter4=0;
while(s[i]!='\0')
{
if(s[i]>=32&&s[i]<=47)
{
++counter1;
}
else if(s[i]>='A'&&s[i]<='Z')
{
++counter2;
}
else if(s[i]>='a'&&s[i]<='z')
{
++counter3;
}
else if(s[i]>='0'&&s[i]<='9')
{
++counter4;
}
++i;
}
printf(" %d,%d,%d,%d\n",counter1,counter2,counter3,counter4);
return 0;
}
这段代码的功能是统计用户输入字符串中不同类型字符的数量,具体来说是统计:
- 标点符号及特殊字符(ASCII码32-47范围内)的数量
- 大写字母(A-Z)的数量
- 小写字母(a-z)的数量
- 数字(0-9)的数量
代码的工作流程如下:
- 定义一个字符数组
s
用于存储输入的字符串 - 使用
fgets
函数从标准输入(键盘)读取字符串,最多读取99个字符(因为要留一个位置给字符串结束符’\0’) - 定义四个计数器变量,分别用于统计不同类型的字符
- 使用
while
循环遍历字符串中的每个字符,直到遇到字符串结束符’\0’ - 在循环中,通过多分支条件判断字符类型,并对相应的计数器加1
- 循环结束后,打印四个计数器的值,分别对应上述四种字符的数量
需要注意的是,ASCII码32-47范围包含了空格、!、"、#、$、%、&、'、(、)、*、+、,、-、.、/这些字符,代码将它们都归为一类统计。
例如,如果用户输入"Hello World! 123",程序会统计:
- 特殊字符:2个(空格和!)
- 大写字母:1个(W)
- 小写字母:8个(e、l、l、o、o、r、l、d)
- 数字:3个(1、2、3)
输出结果为:2,1,8,3
四、字符串操作函数(需包含 <string.h>
)
1. strcpy(s2, s1)
:
把字符串 s1
的内容复制到 s2
(覆盖 s2
原有内容),需保证 s2
空间足够。
strcpy
陷阱:
必须保证目标数组空间足够!比如:char s1[5], s2[] = "abcdef"; strcpy(s1, s2); // 危险!s1 只有 5 空间,s2 带 '\0' 是 6 字符,会越界写内存
将一个字符串从一个字符数组复制到另一个字符数组,并输出复制后的字符串:
int main(void)
{
int i;
char s[]="hello";
char t[100]; //////!!!!!!拷贝到的空间要比被拷贝的空间大。
while(s[i]!='\0')
{
t[i]=s[i];
++i;
}
t[i]='\0';
// strcpy(t,s); //复制
puts(t);
return 0;
}
这段代码的功能是将一个字符串从一个字符数组复制到另一个字符数组,并输出复制后的字符串。
代码的核心逻辑如下:
- 定义了源字符串数组
s
并初始化为"hello" - 定义了目标字符串数组
t
,预留了足够的空间(100个字符) - 使用
while
循环遍历源字符串s
,直到遇到字符串结束符'\0'
- 在循环中,将源字符串
s
的每个字符依次复制到目标字符串t
的对应位置 - 复制完成后,手动在目标字符串
t
的末尾添加字符串结束符'\0'
- 最后使用
puts
函数输出复制后的目标字符串t
代码中特别注释了"拷贝到的空间要比被拷贝的空间大",这是字符串复制的重要原则,以避免目标数组空间不足导致的缓冲区溢出问题。
对于示例中的"hello"字符串,它包含5个有效字符加1个结束符,共6个字符,而目标数组t
有100个字符的空间,完全满足复制需求,因此程序会成功输出"hello"。
代码中还注释了strcpy(t,s);
,这是C语言标准库string.h
中提供的字符串复制函数,其功能与这段代码实现的逻辑相同,都用于将源字符串复制到目标字符串。使用strcpy
函数时同样需要保证目标数组有足够的空间,并且需要在代码开头添加#include <string.h>
。
这种字符串复制方式的关键是不仅要复制有效字符,还要记得在目标字符串的末尾添加结束符'\0'
,否则可能导致字符串处理函数无法正确识别字符串的结束位置。
2. strcat(s2, s1)
:
把字符串 s1
拼接到 s2
末尾(需保证 s2
有足够空间容纳拼接后内容)。
strcat
陷阱:
目标数组要先有有效字符串(必须以'\0'
结尾),且要预留拼接后的空间:char s1[10] = "a", s2[] = "bc"; strcat(s1, s2); // 安全(s1 空间够:"a"+"bc" = "abc" + '\0',总长度 4 ≤ 10)
字符型数组的拼接:
int main(void) //字符型数组的拼接。
{
int i=0,j=0;
char a[100]="Hello";//目标数组要确定有足够大的空间。
char b[100]="World";
while(a[i]!='\0')
{
++i;
}
while(b[j]!='\0')
{
a[i]=b[j];
++j;
++i;
}
a[i]='\0';
puts(a);
// strcat(a,b);
return 0;
}
这段代码的功能是实现两个字符串的拼接(连接)操作,将第二个字符串的内容追加到第一个字符串的末尾。
代码的核心逻辑如下:
- 定义了两个字符数组
a
和b
,分别初始化为"Hello"和"World" a
作为目标数组,需要有足够大的空间来容纳拼接后的字符串(注释中特别强调了这一点)- 首先通过第一个
while
循环找到a
字符串的结束位置(即'\0'
所在的索引),此时i
的值就是a
的长度 - 然后通过第二个
while
循环将b
字符串的每个字符依次复制到a
字符串结束位置的后面- 每次循环将
b[j]
赋值给a[i]
- 同时递增
i
和j
,实现位置后移
- 每次循环将
- 拼接完成后,在新的字符串末尾添加
'\0'
作为结束标志 - 最后使用
puts
函数输出拼接后的字符串
对于示例中的两个字符串,拼接后会形成"HelloWorld"并输出。
代码中还注释了strcat(a,b);
,这是C语言标准库string.h
中提供的字符串拼接函数,其功能与这段代码实现的逻辑完全一致。使用strcat
函数时需要注意:
- 目标数组
a
必须有足够的空间容纳原字符串a
和b
的内容以及结束符 - 需要在代码开头添加
#include <string.h>
才能使用该函数
这种字符串拼接方式的关键是先找到第一个字符串的结束位置,再从该位置开始追加第二个字符串的内容,最后别忘了添加新的结束符。
3. strcmp(s1, s2)
:
按ASCII 码逐字符比较两个字符串:
- 返回
<0
:s1
小于s2
(前序字符 ASCII 更小)。 - 返回
=0
:s1
和s2
完全相同。 - 返回
>0
:s1
大于s2
(前序字符 ASCII 更大)。
两个字符串比较的原理:
int main(void)
{
char a[100]="hello";
char b[100]="hel";
int i=0;
while(a[i]==b[i]&&a[i]!='\0'&&b[i]!='\0')
{
++i;
}
if(a[i]-b[i]>0)
{
printf("数组a更大");
}
else
{
printf("数组b更大");
}
return 0;
}
这段代码的功能是比较两个字符串的大小,其核心逻辑与C语言标准库中的strcmp
函数类似,用于判断两个字符串的字典顺序关系。
代码的执行过程如下:
-
定义了两个字符串数组
a
和b
,分别初始化为"hello"和"hel" -
使用
while
循环逐个比较两个字符串对应位置的字符:- 循环条件是"当前字符相等"且"两个字符串都未结束"
- 当满足条件时,索引
i
递增,继续比较下一组字符 - 当遇到不相等的字符或任一字符串结束时,循环终止
-
循环结束后,通过比较当前位置字符的ASCII码差值判断大小:
- 如果
a[i] - b[i] > 0
,说明a
字符串更大(在字典顺序中更靠后) - 否则,说明
b
字符串更大或两者相等
- 如果
对于示例中的"hello"和"hel":
- 前3个字符’h’、‘e’、'l’完全相同
- 当
i=3
时,a[3]
是’l’(ASCII码108),而b[3]
是字符串结束符’\0’(ASCII码0) - 计算得
108 - 0 = 108 > 0
,因此会输出"数组a更大"
这种比较方式遵循字典排序规则:
- 先比较第一个字符,不同则直接判断大小
- 若相同则比较第二个字符,以此类推
- 当一个字符串是另一个字符串的前缀时,更长的字符串被认为更大
如果需要更精确的判断(区分相等的情况),可以在条件判断中增加对a[i] == b[i]
的检测,此时会返回0表示两个字符串完全相等。
例题:
在三个字符型数组里找出最大的字符型数组:
#include<string.h>
int main(void) //在三个字符型数组里找出最大的字符型数组。
{
char s1[100]="Hello";
char s2[100]="Hello!";
char s3[100]="Hello!!";
char max[100];
if(strcmp(s1,s2)>0)
{
strcpy(max,s1);
}
else
{
strcpy(max,s2);
}
if(strcmp(max,s3)<0)
{
strcpy(max,s3);
}
puts(max);
return 0;
}
4.strlen
细节:
只算到 '\0'
为止,不包含 '\0'
。比如 str:len("abc")
结果是 3
,而 sizeof("abc")
是 4
(含 '\0'
)。
判断输入的字符串的有效字符个数:
int main(void) //判断有效字符的个数,即除了'\0'外都为有效字符。
{
int i=0;
char s[10]="Hello";
while(s[i]!='\0')
{
++i;
}
printf("%d\n",i);
// strlen(s); //查看数组的有效字符
return 0;
}
这段代码的功能是计算字符串中有效字符的个数(不包括字符串结束符'\0'
)。
代码的核心逻辑如下:
- 定义了一个字符数组
s
并初始化为"Hello" - 使用
i
作为计数器,初始值为0 - 通过
while
循环遍历字符串,条件是当前字符不是结束符'\0'
- 在循环中,每遇到一个有效字符(非
'\0'
),计数器i
就加1 - 循环结束后,
i
的值就是字符串中有效字符的个数,通过printf
输出
对于初始字符串"Hello",它包含5个有效字符(‘H’、‘e’、‘l’、‘l’、‘o’),所以程序会输出5
。
代码中还注释了strlen(s);
,这是C语言标准库string.h
中提供的字符串长度计算函数,其功能与这段代码实现的逻辑完全一致,都用于获取字符串中有效字符的个数(不包括'\0'
)。如果要使用strlen
函数,需要在代码开头添加#include <string.h>
。
这种计算方式的关键是利用了C语言中字符串以'\0'
作为结束标志的特性,通过检测这个结束标志来确定字符串的有效长度。
这些是 C 语言字符串处理的基础,实际开发中要注意 数组越界 和 '\0'
的正确处理,避免程序崩溃或逻辑错误。
4.**字符型数组的逆序
:
int main(void)
{
char s[100]="Hello";
int len,i;
len=strlen(s); //len求的是有效字符的长度。
for(i=0;i<len/2;++i)
{
char t;
t=s[i];
s[i]=s[len-i-1];
s[len-i-1]=t;
}
puts(s);
return 0;
}
这段代码的功能是将字符串进行反转(逆序)处理并输出结果。
代码的核心逻辑如下:
- 定义字符数组
s
并初始化为"Hello" - 使用
strlen(s)
计算字符串的有效长度(不包含结束符'\0'
),并将结果存储在len
中 - 通过
for
循环实现字符串反转:- 循环从
i=0
开始,到i < len/2
结束(只需交换一半的字符即可完成整个字符串的反转) - 在每次循环中,将字符串第
i
个字符与第len-i-1
个字符进行交换 - 使用临时变量
t
作为中间存储,完成两个字符的交换操作
- 循环从
- 反转完成后,使用
puts
函数输出反转后的字符串
对于初始字符串"Hello":
- 其有效长度
len
为5 - 循环会执行2次(
i=0
和i=1
):- 第一次交换:
s[0]
(‘H’)与s[4]
(‘o’)交换 - 第二次交换:
s[1]
(‘e’)与s[3]
(‘l’)交换 - 中间的字符
s[2]
(‘l’)不需要交换
- 第一次交换:
- 最终反转后的字符串为"olleH",程序会输出该结果
这种字符串反转方法的时间复杂度是O(n)(n为字符串长度),空间复杂度是O(1),属于高效的原地反转算法,不需要额外的存储空间来存放反转后的字符串。
需要注意的是,使用strlen
函数需要在代码开头添加#include <string.h>
头文件。
课后作业:
编写程序实现一个atoi的功能,将一个整数转换为字符串
int n = 1357;
char s[100];在数组中保存字符串"1357"
int main(void)
{
int input;
printf("请输入一个你要转换为字符的整数:\n");
scanf("%d",&input);
char s[100];
int i=0;
while(input!=0)
{
s[i]=input%10+'0';
input/=10;
++i;
}
s[i]='\0';
int len=strlen(s);
for(i=0;i<len/2;++i)
{
char t;
t=s[i];
s[i]=s[len-1-i];
s[len-1-i]=t;
}
puts(s);
return 0;
}
这段代码的功能是将用户输入的整数转换为对应的字符串并输出。
代码的核心逻辑分为两个主要步骤:
-
将整数拆分并存储为字符:
- 通过
while
循环对输入的整数input
进行处理 - 每次循环使用
input%10
获取最后一位数字,然后加上'0'
将其转换为对应的字符(利用ASCII码的特性) - 之后通过
input/=10
移除已经处理的最后一位数字 - 将转换后的字符依次存入数组
s
中,索引i
递增
- 通过
-
反转字符数组:
- 由于第一步得到的字符顺序是逆序的(先获取的是数字的低位),需要进行反转
- 计算字符数组长度
len
- 通过
for
循环交换对称位置的字符(第i
个和第len-1-i
个),实现整个字符串的反转 - 最后添加字符串结束符
'\0'
例如,如果用户输入整数12345
:
- 第一步处理后得到的字符数组是
['5','4','3','2','1','\0']
- 反转后得到
['1','2','3','4','5','\0']
- 最终输出字符串"12345"
需要注意的是:
- 这段代码无法处理输入为0的情况(因为
while(input!=0)
循环不会执行) - 使用
strlen
函数需要包含#include <string.h>
头文件 - 代码假设输入的是正整数,对于负整数会出现转换错误
如果要完善这段代码,需要添加对0和负整数的处理逻辑。