extern void swap(int a,int b);
int main(){
int a = 10;
int b = 20;
swap(a,b);
printf("%d\n",a); 结果为10
printf("%d\n",b); 结果为20
return 0;
}
void swap(int a,int b){
int temp = a;
a = b;
b = temp;
}
以上代码中,将a b直接传递给函数swap,进行值对调的操作之后,并没有改变最终的a和b的值,
这是因为在C语言中,值传递这种方式,形参不影响实参的值
原理是:
1、在内存中分配a的内存空间
2、在内存中分配b的内存空间
3、在内存中加载swap函数
4、在内存中分配形参a,b,temp
5、将步骤4中分配的a和b值进行对调
6、swap函数结束,方法出栈,内存中将步骤3 4 5的操作痕迹进行销毁
7、打印a b的值依然为10 20
extern void swap(int* a,int* b);
int main(){
int a = 10;
int b = 20;
swap(&a,&b);
printf("%d\n",a); 结果为20
printf("%d\n",b); 结果为10
return 0;
}
void swap(int* a,int* b){
int temp = *a;
*a = *b;
*b = temp;
}
以上代码中,将a b的地址作为参数传递给函数swap,进行值对调的操作之后,最终改变了a和b的值
这是因为地址传递的方式,形参可以改变实参的值
原理是:
1、在内存中分配a的内存空间
2、在内存中分配b的内存空间
3、在内存中加载swap函数
4、在内存中分配形参指针a,指针b,int类型变量temp
5、将指针a对应的值10,赋值给temp
6、将指针b对应的值20,赋值给指针a对应的地址,这里并没有对指针a进行什么操作,而是通过指针a间接的改变步骤1中a的值
7、将temp的值10,赋值给指针b对应的地址,改变的也是步骤2中b的值
8、swap函数结束,方法出栈,内存中销毁步骤34567的数据
9、此时步骤1 2中的值已经发生了改变 a = 20, b = 10
同样的道理,之前学的冒泡排序函数,字符串复制函数,进行的都是地址传递,所以能够在方法出栈之后,依然能改变变量的值
数组名做函数的参数,函数的形参(数组名)会退化为指针
例子:给一个字符串ch1后面追加另一个字符串ch2:
方法一:数组思路:
extern void my_strcat(char* ch1,char* ch2);
int main(){
char ch1[100] = "hello";
char ch2[] = "world";
my_stradd(ch1,ch2); 将ch2的内容添加到ch1后面
printf("%s\n",ch1);
return 0;
}
void my_strcat(char* ch1,char* ch2){
int i = 0;
while(ch1[i] != '\0'){
i++;
}
int j = 0;
while(ch2[j] != '\0'){
ch1[i+j] = ch2[j];
j++;
}
}
问题:之前说数组名作为函数参数的时候要连同数组长度也一起传进来,为啥他就不用传递数组长度进来呢?
解答:因为字符数组比较特殊,就像代码中ch1分配的长度是100,但是我们有更精确的方式来判断字符串的长度,就是判断’\0’,再一个,此处如果将100传进来, 确实起不到什么作用
问题:第二个while循环之后,还没有给字符串末尾加’\0’,是不是应该加一行:
ch1[i+j] = '\0';
解答:理论上应该加,但是这里可以不用写,因为main函数中ch1的长度定义了100,这么多位置除了hello之外,其他的位置全是0,所以这一行已经帮我们做了,我们可以不用写
方法二:指针思路
void my_stradd(char* ch1,char* ch2){
int i = 0;
while(*(ch1 + i) != '\0'){
i++;
}
int j = 0;
while(*(ch2 + j) != '\0'){
*(ch1 + i + j) = *(ch2 + j);
j++;
}
}
就是将方法一中的数组名+下标 变成 指针+偏移量
方法三:利用指针自增代替指针偏移量
void my_stradd(char* ch1,char* ch2){
while( *ch1 )ch1++;
while(*ch2){
*ch1 = *ch2;
ch1++;
ch2++;
}
}
方法四:精简指针自增赋值
void my_stradd(char* ch1,char* ch2){
while( *ch1 )ch1++;
while(*ch1++ = *ch2++);
}
第二行代码中,包含的操作有:
1、*ch1 = *ch2;
2、ch1++; ch2++; 这里涉及到运算符优先级的问题
3、while(*ch1 != 0)
例子:字符串去空格
方法一:数组思路:遍历原字符数组,遇到不是空格的,将他放进一个新的字符数组中,最后拼成一个新的字符数组
extern void str_remove_space(char* ch);
int main(){
char ch[] = "h e ll o w o r l d";
str_remove_space(ch);
return 0;
}
void str_remove_space(char* ch){
char str[100] = {
0}; 定义一个字符数组,用来存放过滤后的字符串
int i = 0,j = 0;
while(ch[i] != '\0'){
if(ch[i] != ' '){
str[j] = ch[i]; 给新数组赋值
j++;
}
i++;
}
printf("%s\n",str);
}
第一个方法中,使用指针思路也一样,无论怎么改原理都差不多,但是这里面使用到了一个新创建的数组,这种办法并不好,万一我传入的非空格字符多于100就完了
所以可不可以不用创建这样一个新的字符数组呢?
方法二:
思路:定义指向字符数组的两个指针,一个指针一直往前遍历数组,另一个指针指向数组起始位置,第一个指针遍历过程中,遇到不是空格的字符,就将第二个指针, 指向的位置替换成这个字符,指针++
void str_remove_space(char* ch){
char* i = ch; 这里将ch本身作为第一个指针,将i作为第二个指针
while(*ch != '\0'){
if(*ch != ' '){
*i = *ch;
i++;
}
ch++;
}
*i = 0; 将第二个指针最后一位写0
}
例子:字符串中查找一个字符,并且返回该字符所在的指针
extern char* my_strchr(char* str,char ch);
int main(){
char* str = "hello world";
char* p = my_strchr(str,'r');
if(p == NULL){
printf("未找到\n");
}else{
printf("%s\n",p);
}
return 0;
}
char* my_strchr(char* str,char ch){
while(*str){
if(*str == ch){
return str;
}
str++;
}
return NULL;
}
例子:字符串中查找另一个字符串,并且返回该字符串所在的指针
分析:
首先需要一个指针1,用来遍历源字符串
再定义一个指针2,用来遍历目标字符串
还需要定义一个指针3,用来指向源字符串中和目标字符串相匹配的第一个位置,用来全部匹配上之后返回的
思路:
遍历源字符串,如果和目标字符串的第一个字符相等,则保存指针3,开始比较源字符串和目标字符串之后的内容,最后匹配上了,返回指针3即可
extern char* my_strstr(char* src,char* dest);
int main(){
char src[] = "hello world";
char dest[] = "wo";
char* p = my_strstr(src,dest);
if(p == NULL){
printf("null\n");
}else{
printf("%s\n",p);
}
return 0;
}
char* my_strstr(char* src,char* dest){
char* fsrc = src; 遍历src的指针
char* fdest = dest; 目标字符串指针
char* rsrc = src; 待返回指针
while(*fsrc){
判断是否读到了字符串末尾
rsrc = fsrc; 待返回指针先跟着遍历指针走
while(*fsrc == *fdest && *fsrc != '\0'){
判断当遍历指针的值和目标指针的值一样的话,待返回指针就不动了,while开始比较当前
指针指向的值和目标指针指向的值,如果一样,指针自加,继续比对,如果不一样,跳出循
环,*fsrc != '\0'是防止目标字符串刚好和源字符串的末尾相同,导致数组越界,影响
结果
fsrc++;
fdest++;
}
if(*fdest == '\0'){
跳出循环之后,判断目标指针是不是指向字符串末尾,用来判断目标字符串是不是完全匹配
return rsrc;
}
//如果没匹配上,回滚
fsrc = rsrc; 如果没有匹配上,源字符串指针要指到一开始和目标字符串开始比较的地方
fdest = dest; 目标指针也要指向目标字符串开始的地方
fsrc++; 源字符串自增,开始下一轮查找
}
return NULL;
}
例子:定义一个字符串
char ch[] = "hello world";
char *p = ch;
考虑:既然如上代码是成立的,那么可以不可以这样定义一个字符串呢?
char* p = "hello world"; 将指针直接指向字符串
这样是可以,不会报错,而且打印和获取单个字符都可以执行
但是还是有区别的地方的,例如,修改字符串中的某个字符
char ch[] = "hello world";
ch[2] = 'm';
printf("%s\n",ch); 打印结果:hemlo world
char *p = "hello world";
*(p+2) = 'm';
printf("%s\n",*p); 会报错
报错原因在于:
char ch[] = "hello world"; 这种定义方式,定义的是栈区字符串
char* p = "hello world"; 这种定义方式,定义在数据区常量区字符串
栈区定义的字符串可以修改,常量区字符串不允许修改,
例如最开始的printf(“hello world”);中的字符串就是存储在数据区常量区的
另外一个例子可以直观的理解数据区常量区字符串的概念:
char ch[] = "hello world";
char *p = "hello world";
char *p1 = "hello world";
printf("%p\n",ch); 0028FF2C
printf("%p\n",p); 00405064
printf("%p\n",p1); 00405064
可以看出,通过指针定义的字符串打印的地址相同,说明指针定义字符串是常量,可以改变指针p的值,但是不能改变指针指向地址的值(*p)
指针指向的区域是常量,只读的,所以多个指针指向同一个字符串地址是一样的
定义字符数组
方式一:
char ch1[] = "hello";
char ch2[] = "world";
char ch3[] = "dabaobei";
char* arr[] = {
ch1,ch2,ch3};
这种方式定义的字符串数组是可以改变字符串的值的,根据上面的例子,定义不能改变字符串的值的方法为:
char* arr[] = {
"hello","world","dabaobei"};
遍历字符串数组中的元素:
for(int i = 0;i<3;i++)
{
printf("%s\n",arr[i]);
}
但是怎么样给arr中的元素排序呢?
如果定义方式是方式一的话,是不能进行排序的,因为前两个字符串长度一样,可以进行交换,第三个字符串和前两个字符串长度不一样,无法进行调换,那下面的方式二有什么办法呢?
char* arr[] = {
"hello","world","dabaobei"};
分析:方式二的字符串是存放在数据区常量区的,无法对字符串本身进行改变,但是每个字符串有对应的内存地址:如0x10 0x20 0x30分别存放hello world dabaobei
而在另一区域存储着指针变量arr,arr[0]的值为0x10,arr[1]的值为0x20,arr[2]的值为0x30
而排序必定会用到冒泡排序这些方法,肯定会涉及到两个首字母进行比较,然后两个进行位置调换
试想如果hello和world两个字符串需要调换位置,那么只需要将arr[0]的值和arr[1]的值进行调换就可以了,此时arr[0] arr[1] arr[2]对应的值分别为:
0x20 0x10 0x30
遍历arr中的值,第一个出来的就是0x20地址所对应的字符串,可以实现排序
char* arr[] = {
"hello","world","dabaobei"};
for(int i = 0;i<3;i++)
{
for(int j = 0;j<3-1-i;j++)
{
if(arr[j][0]>arr[j+1][0]) 找首字符进行比较
{
char* temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
for(int k = 0;k<3;k++)
{
printf("%s\n",arr[k]); 这里arr[k]也是指针类型,为啥打印的时候前面不用*取值符号呢?因为前面占位符写的是%s,这样的占位符会根据
arr[k]所在的指定位置一直往后寻址取值打印,知道找到\0停止
}
运行结果为 hello world dabaobei
计算字符串的有效长度
数组思路:
int my_strlen(char* ch)
{
int i = 0;
while(ch[i]!='\0')
{
i++;
}
return i;
}
指针思路
int my_strlen(char* ch)
{
char* temp = ch; 定义一个辅助指针,和ch指向同一位置
while(*(temp))temp++; 只要*temp不是0,temp一直自增
return temp-ch; 最后temp的位置指向字符串结尾的\0,ch指向第一个字符,两者间的差值就是字符串的长度
}
一般情况下,像现在这样,我们想要获取到一个字符串的信息,例如长度信息,是不需要改变字符串的内容的,为了安全起见,可以使用const修饰函数的参数
需要注意的是,我们使用const修饰的目的是为了不让字符串的值发生改变,所以这里const应该修饰的是char*
int my_strlen(const char* ch)
第一种方式:
char ch1[] = "hello";
char ch2[] = "world";
const char* p = ch1; 这种情况下,const修饰的是char*,所以不可以修改p指向的字符串的内容,可以修改p指向的位置
也就是说:
*p = 'm'; err 这一行代码是想将h改成m
p[2] = 'm'; err 这个原因和上一行是一样的
p = ch2; ok
这种使用const修饰指针类型(char*)的方式,我们称之为指向常量的指针
第二种方式:
char ch1[] = "hello";
char ch2[] = "world";
char* const p = ch1; 这种情况下,const修饰的是变量p,所以不可以修改p的指向,而可以修改*p也就是字符串的内容
*p = 'm'; ok
p = ch2; err
这种使用const修饰指针变量(p)的方式.我们称之为指针常量
第三种方式:
char ch1[] = "hello";
char ch2[] = "world";
const char* const p = ch1; 这种情况下,const修饰char*也修饰指针变量p,既不能改变p的指向,也不能改变字符串的内容
*p = 'm'; err
p = ch2; err
这种既使用const修饰指针类型又使用const修饰指针变量的方式,我们称之为只读指针
相同的道理,一级指针的常量,可以使用二级指针进行修改,是可以的
例如,想要将hello变成hmllo,使用二级指针修改
char ch1[] = "hello";
const char* const p = ch1;
char** p1 = &p; 定义一个二级指针,指向一级指针p的地址
*(*p1+1) = 'm'; p1是二级指针,*p1就是一级指针,一级指针就很ch1一样,所以*p1加上偏移量,扩住再取值就是字符串的值
二级指针常量可以用三级指针进行修改,以此类推,所以说const修饰指针这样的方式是怎么样都不安全的,
但是在指针作为函数参数的时候,也只能加一个const约束一下