【C语言入门】笔记十 (指针中)

- 接上篇 - 

要通过函数调用来改变主调函数中的某个变量的值,可以将指针作为函数的参数。在主调函数中,将该变量的地址或者指向该变量的指针作为实参。再被调用函数中,用指针类型形参接受该变量的地址,并改变形参所指向变量的值。

而函数只通过return语句只能返回一个值,如果希望函数调用能将多个计算结果带回主调函数,用return语句是无法实现的,而将指针作为函数的参数就能返回(改变)多个指针指向的内存地址对应的多个变量的多个值。

练习8-2

调用函数求两个数的和与差:计算输入的两个数的和与差,要求自定义一个函数sum_diff(float op1, float op2, float *psum, float *pdiff),其中op1和op2是输入的两个数,*psum*pdiff是计算得出的和与差。

#include <stdio.h>
void sum_diff( float op1, float op2, float *psum, float *pdiff );
int main(void){
    float a, b, sum, diff;
    scanf("%f %f", &a, &b);
    sum_diff(a, b, &sum, &diff);
    printf("The sum is %.2f\nThe diff is %.2f\n", sum, diff);
    return 0; 
}
void sum_diff(float op1, float op2, float *psum, float *pdiff){
	*psum=op1+op2;
	*pdiff=op1-op2;
} 

8.3 冒泡排序

指针和数组在许多方面有相似之处,例如指针名和数组名都代表内存地址。而指针名是一个变量,而数组名是一个常量。即指针名所代表的地址是可以改变的,而数组一旦被定义后内存空间就被分配,也就是说数组名所代表的地址是不能改变的。因此研究指针和数组的关系类似于研究一个同类型的变量和常量之间的关系,但前者更复杂。

例8-5 输入n个正整数,将它们从小到大排序后输出,要求使用冒泡排序的算法。

#include <stdio.h>
void bubble(int a[], int n);
int main(void){
	int n, a[8];
	int i;
	
	printf("Enter n (n<=8):");
	scanf("%d", &n);
	printf("Enter a[%d]:",n);
	for(i=0;i<n;i++){
		scanf("%d",&a[i]);
	}
	bubble(a,n);
	printf("After sorted, a[%d]=",n);
	for(i=0;i<n;i++){
		printf("%3d",a[i]);
	}
	return 0;
}
void bubble(int a[], int n){
	int i,j,t;
	for(i=1;i<n;i++){
		for(j=0;j<n-1;j++){
			if(a[j]>a[j+1]){
				t=a[j];
				a[j]=a[j+1];
				a[j+1]=t;
			}
		}
	}
}

程序中定义了一个函数bubble()实现数组元素的排序。它有两个形参,a是等待排序的整形数组名,n指明数组a待处理的数组元素的数量。

冒泡排序算法分析

之所以叫冒泡排序,是因为在进行从小到大的排序时,小的数会经过交换会慢慢从地底下"冒"上来。冒泡排序效率不高,这是因为它要约n²/2次比较,然而对一些小数组来说,它的性能通常还是可以接受的。

冒泡法的算法过程如下:

练习 8-3

两个相同类型的指针变量能不能相加?为什么?

答:可以,不过相加没有意义,相减也可以,相减可以代表某两个内存地址之间相隔了多少内存单元。

练习 8-4

如冒泡排序法算法过程表所示,在排序到第6遍的时候已经排序好了,说明有的时候不一定要n-1次循环,如何改进冒泡排序法?(若发现一次遍历循环后数据没有发生变化,说明已经排好了)

#include <stdio.h>
void bubble(int a[], int n);
int main(void){
	int n, a[8];
	int i;
	
	printf("Enter n (n<=8):");
	scanf("%d", &n);
	printf("Enter a[%d]:",n);
	for(i=0;i<n;i++){
		scanf("%d",&a[i]);
	}
	bubble(a,n);
	printf("After sorted, a[%d]=",n);
	for(i=0;i<n;i++){
		printf("%3d",a[i]);
	}
	return 0;
}
void bubble(int a[], int n){
	int i,j,t,unchange;
	for(i=1;i<n;i++){
		unchange=0; //加个计数器,记录没有数据交换的次数
		if(unchange!=n){
			for(j=0;j<n-1;j++){
				if(a[j]>a[j+1]){
					t=a[j];
					a[j]=a[j+1];
					a[j+1]=t;
				}else{
					unchange++;
				}
			}
		}else{
			break;
		} 
	}
}

(增加一个计数的变量,记录数据没有交换的次数,当次数等于输入的n时,就跳出循环)

练习 8-5

重做例 8-5,要求使用选择排序算法。

(回顾文章:【C语言入门】笔记七​​​​)

#include <stdio.h>
int main(void){
	int i,j,n,index,temp,a[8];
	scanf("%d",&n);
	for(i=0;i<=n-1;i++){
		scanf("%d",&a[i]);
	}
	for(i=0;i<=n-1;i++){
		index=i;
		for(j=i+1;j<=n-1;j++){
			if(a[index]>a[j]){
				index=j;
			}
		}
		temp=a[index];
		a[index]=a[i];
		a[i]=temp;
	}
	for(i=0;i<=n-1;i++){
		printf("%d ",a[i]);
	}
	return 0;
} 

8.4 电码加密

C语言中,字符串是一种特殊的char型一维数组,可以把字符串中的字符作为数组中的元素访问,或者使用char型指针对其访问,这种灵活性使得在用C语言编写的字符串程序时特别有用。在C语言的标准库中提供了很多有用的字符串处理函数。

例8-9 为了防止信息被他人轻易盗取,需要把电码明文通过加密方式变换成为密文。变换规则:小写字母z变换成a,其他字母变换成该字母ASCII码顺序后的以为字符,如o变换成p。

#include <stdio.h>
#include <string.h>
#define MAXLINE 100
void encrypt(char *s);
int main(void){
	char line[MAXLINE];
	printf("Input the string:");
	gets(line);
	encrypt(line);
	printf("%s%s\n","After being encrypted:",line);
	return 0; 
}
void encrypt(char *s){
	for(;*s!='\0';s++){
		if(*s=='z'){
			*s='a';
		}else{
			*s+=1;
		}
	}
}

字符数组line用于存放输入的字符串,调用加密函数encrypt()后,输出加密后的密文。加密函数的形参时字符指针s,

这里补充说明一下,数组变量的 line 本身就是一个指针,所以调用 encrypt 函数时不使用 &line ,所以这里的 encrypt(line) 等价于 encrypt(&line[0]

在调用时接受数组line的值(为数组首元素的地址),通过指针s的一定实现对字符数组中的每一个元素遍历和处理。

8.4.2 字符串和字符指针

字符串常量的存储:字符串常量使用一对双引号括起来的字符序列,与基本类型常量的存储相似,字符串常量在内存中的存放位置由系统自动安排。由于字符串常量是一串字符,通常被看作一个特殊的一位字符数组,与数组的存储类似,字符串常量中的所有字符在内存中连续存放。所以,系统在存储一个字符串常量时先给定一个起始地址,从该地址指定的存储单元开始,连续存放该字符串中的字符。

显然,该起始地址代表了存放字符串常量首字母的存储单元的地址,被称为字符串常量的值,也就是说,字符串常量实质上是一个指向该字符串首字符的指针常量。

例如字符串"Hello"的值是一个地址,从它指定的存储单元开始连续存放该字符串的6个字符('H' 'e' 'l' 'l' 'o' '\n')

如果定义一个字符指针接收字符串常量的值,该指针就指向字符串的首字符,例如:

char sa[]="array ";
char *sp="point ";
printf("%s", sa); //数组名sa作为printf的输出参数
printf("%s", sp); //字符指针sp作为printf的输出参数
printf("%s \n", "string"); //字符串常量作为printf的输出参数

输出:array point string

注:在Visual Studio中,char *sp="point";这种写法不可以使用,可以用const char *sp="point";代替。

调用printf()函数,以%s的格式输出字符串时,作为输出参数,数组名sa指针sp字符串"string"的值都是地址,从该地址所指定的单元开始连续输出其中的内容(字符),直到遇到'\0'为止。由此可见,输出字符串时,输出参数给出的起始位置(地址)'\0'用来控制结束。因此,字符串中其它字符的地址也能作为输出参数。例如:

printf("%s", sa+2); //数组元素sa[2]的资质作为输出参数
printf("%s", sp+3); //sp+3作为起始数组
printf("%s \n", "string" +1 ); //t作为起始输出

字符数组与字符指针都可以处理字符串,但两者之间有重要区别,如:

char sa[]="This is a string";
char* sp="This is a string";

字符数组sa在内存中占用了一块连续的单元,有确定的地址,每个数组元素放字符串的一个字符,字符串就存放在数组中。字符指针sp只占用一个可以存放地址的内存单元,而不是将字符串放到字符指针变量中去

如果要改变数组sa所代表的字符串,只能改变数组元素的内容。如果要改变指针sp所代表的字符串,通常直接改变指针的值,让它指向新的字符串。因为sp是指针变量,它的值可以改变,转向指向其它单元,如:

strcpy(sa,"Hello");
sp="Hello";

分别改变了sa和sp所代表的字符串,而 sa="Hello"; 是非法的,因为数组名是常量,不能对它赋值。

当定义字符指针后,如果没有赋值,指针的值是不确定的,引用未赋值的指针可能出现难以预料的结果:

char* s;
scanf("%s",s);

没有对指针s赋值,却对s指向的单元赋值,如果该单元已经分配给其他变量,其值就改变了。所以以下写法str有确定的存储单元,这才是正确的:

char* s,str[20];
s=str;
scanf("%s",s);

为了避免引用未赋值的指针造成的危害,在定义指针时,可先将它的初值置为空,如:char* s=NULL;

#include<stdio.h>
int main(void){
    //字符串指针
    const char* s = "Hello World!";
    printf("%s\n", s);
    //字符串数组
    char ch[] = "Hello World!";
    printf("%s\n", ch);
    //整型变量和指针
    int* i, a = 233333;
    i = &a;
    printf("%d\n", *i);
    //字符型变量和指针
    char* cha, b = 'a';
    cha = &b;
    printf("%c\n", *cha);
    return 0;
}

在处理整型和字符型等时,printf()函数输出时指针变量前需要带上 *

而处理字符串时,输出时指针变量前则不需要* 号。

8.4.3 常用的字符串处理函数

在C语言的标准库中含有许多非常有用的字符串处理函数。它们都要求字符串作为参数,并且它们的都返回整数值或者指向char的指针。在头文件stdio.hstring.h中给出了字符串处理函数原型,所以使用这些字符串处理函数时要引入相应的头文件。

1. 字符串的输入和输出

函数scanf()gets()可用来输入字符串,而printf()puts()输出字符串。它们在系统文件stdio.h中定义。

(1) scanf(格式控制字符串,输入参数表)

格式控制字符串中使用格式控制说明%s,输入参数必须是字符型数组名。该函数遇到回车或空格输入结束,并自动将输入的数据和字符串结束符'\0'送入到数组中。如:

scanf("%s", s); //假设s为字符型数组

(2) printf(格式控制字符串,输出参数表)

格式控制字符串中相应的格式控制说明%s,输出参数可以是字符数组或字符串常量。输出遇到'\0'结束,如:

printf("%s",s);

由于在C语言中是使用一维字符数组存放字符串的,因此可以在字符串操作函数中直接使用数组名进行输入输出(有点不懂...)

(3) 字符串输入函数 gets(s)

参数s是字符数组名,函数从输入得到一个字符串,遇到回车输入结束,自动将输入的数据和'\0'送入数组中。采用函数gets()输入的字符串允许带空格

函数gets()有返回值,如果输入成功则返回值是字符串的第一个字符的地址,如果输入失败则返回NULL。但一般情况下使用gets()主要是为了输入字符串,并不关心它的返回值。

(4) 字符串输出函数 puts(s)

参数s可以是字符数组名或字符串常量。输出时遇到'\0'自动将其转换为'\n',即输出字符串后换行。同样函数puts()也有返回值,如果成功执行了输出字符串的操作,则返回换行符号'\n',否则返回EOF

以上组成了两组输入输出函数,虽然都可以达到输入输出字符串的目的,但还是有一些细微的差别。

#include<stdio.h>
int main(void){
	char str[80];
	scanf("%s", str);
	printf("%s", str);
	printf("Hello");
	return 0;
}

#include<stdio.h>
int main(void){
	char str[80];
	gets(str);
	puts(str);
	puts("Hello");
	return 0;
}

区别一:函数puts()输出字符串后会自动更换行,而函数printf()不会

区别二:函数scanf()遇到空格结束输入,而函数gets()遇到回车符结束,所以函数scanf()不能输入带空格的字符串

2. 字符串的赋值、连接和比较以及字符串长度

字符串赋值、连接和比较以及计算字符串的长度函数再系统头文件string.h中定义。

(1) 字符串的赋值函数char* strcpy(char* s1, char* s2); 

该函数把字符串s2复制到s1,直到遇到s2中的 '\0' 为止,函数返回的是s1,简化表达式为

strcpy(s1,s2);

其中,参数s1必须是字符型数组的基地址,参数s2可以是字符数组名或字符串常量,如:

#include <stdio.h>
#include <string.h>
int main(void){
		char s1[80], s2[80], from[64] = "happy";
		strcpy(s1, from);
		strcpy(s2, "Hello");
		puts(s1);
		puts(s2);
	return 0;
}

(2) 字符串连接函数 strcat(s1, s2)

参数s1必须是字符数组基地址,参数s2可以是字符数组名或字符串常量。strcat()函数将字符串s2接到字符串s1的后面,此时s1中原有的结束符'\0'被放置再连接后的结束位置上。数组s1的长度要足够大。

#include <stdio.h>
#include <string.h>
int main(void){
	char s1[80]="Hello ", s2[80]="World";
	strcat(s1, s2);
	puts(s1);
	return 0;
}

C语言中不同于如JavaScript等一些编程语言,C语言中不允许使用算术运算将字符数组直接相连,即str1=str2+t+"!"非法

(3) 字符串比较函数 strcmp(s1, s2)

函数strcmp()中的s1s2都可以是字符数组名或者字符串常量。函数strcmp()返回一个整数,给出字符串s1和s2的比较结果:

  1. 若s1与s2相等,返回0
  2. 若s1大于s2,返回一个正数
  3. 若s1小于s2,返回一个负数

字符串比较的规则是:从两个字符串的首字符开始,依次比较相对应的字符的ASCII码,直到出现不同的字符或者遇到'\0'时停止。若所有字符都相同,返回0否则以第一个不相同的字符的比较结果为准,返回这两个字符的差。(即第个字符串中的第一个不相同的字符的ASCII码减去第个字符串中的第一个不相同的字符的ASCII码得到的差。)

例如:

  • strcmp("sea", "sea") 的返回值是0,表明两个字符串的值相等
  • strcmp("compute", "compare") 的返回值是'u'-'a'的值大于0,表明"compute"大于"compare"

(4) 字符串长度函数 strlen(s)

参数s可以是字符数组名或字符串常量。函数strlen()返回字符串s的'\0'之前的字符个数,即字符串有效字符的个数(不包括'\0'),例如strlen("happy")的返回值是5

表8.3 常用的字符串处理函数例子 

在用这些函数之前,必须要提供函数原型,在头部引入头文件string.h(#include <string.h>)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值