lesson 7 指针和数组


补充之前ifndef部分

它是if not define 的简写,是宏定义的一种,实际上确切的说,这应该是预处理功能三种(宏定义、文件包含、条件编译)中的一种----条件编译。

  在c语言中,对同一个变量或者函数进行多次声明是不会报错的。所以如果h文件里只是进行了声明工作,即使不使用# ifndef宏定义,多个c文件包含同一个h文件也不会报错。

  但是在c++语言中,#ifdef的作用域只是在单个文件中。所以如果h文件里定义了全局变量,即使采用#ifdef宏定义,多个c文件包含同一个h文件还是会出现全局变量重定义的错误。

使用#ifndef可以避免下面这种错误:如果在h文件中定义了全局变量,一个c文件包含同一个h文件多次,如果不加#ifndef宏定义,会出现变量重复定义的错误;如果加了#ifndef,则不会出现这种错误。

#ifndef x                 //先测试x是否被宏定义过
#define x
程序段1blabla~    //如果x没有被宏定义过,定义x,并编译程序段 1
#endif     程序段2blabla~   //如果x已经定义过了则编译程序段2的语句,“忽视”程序段 1

条件指示符#ifndef 的最主要目的是防止头文件的重复包含和编译。了解:条件编译当然也可以用条件语句来实现。 但是用条件语句将会对整个源程序进行编译,生成的目标程序程序很长,而采用条件编译,则根据条件只编译其中的程序段1或程序段2,生成的目标程序较短。如果条件选择的程序段很长,采用条件编译的方法是十分必要的。

#ifndef 和 #endif 要一起使用,如果丢失#endif,可能会报错。总结一下:在c语言中,对同一个变量或者函数进行多次声明是不会报错的。所以如果h文件里只是进行了声明工作,即使不使用# ifndef宏定义,一个c文件多次包含同一个h文件也不会报错。 使用#ifndef可以避免下面这种错误:如果在h文件中定义了全局变量,一个c文件包含同一个h文件多次,如果不加#ifndef宏定义,会出现变量重复定义的错误;如果加了#ifndef,则不会出现这种错.

#ifdef

  与ifndef类似,ifdef顾名思义,就是if define,看例子

#ifdef  x
 程序1blabla~
#endif

  翻译:如果宏定义了x,则执行程序1.

  此外,还有其他形式,还是看例子好些:

#ifndef  x
#define x
 程序段 1
#else
 程序段 2
#endif

  当x没有由#define定义过,则编译“程序段1”,否则编译“程序段2”。

#if 表达式
 程序段 1
#else
 程序段 2
#endif

  它的作用是 当“表达式”值为真时。编译程序段1。否则则编译程序段2。当没有程序段2时,直接是#if---#endif

#define

  在C或C++语言源程序中允许用一个标识符来表示一个字符串,称为“宏”。“define”为宏定义命令。

  被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。

  优点:

    (1) 方便程序的修改。这个就不多说了。

    (2) 提高程序的运行效率。使用带参数的宏定义可完成函数调用的功能,又能减少系统开销,提高运行效率。正如C语言中所讲,函数的使用可以使程序更加模块化,便于组织,而且可重复利用,但在发生函数调用时,需要保留调用函数的现场,以便子函数执行结束后能返回继续执行,同样在子函数执行完后要恢复调用函数的现场,这都需要一定的时间,如果子函数执行的操作比较多,这种转换时间开销可以忽略,但如果子函数完成的功能比较少,甚至于只完成一点操作,如一个乘法语句的操作,则这部分转换开销就相对较大了,但使用带参数的宏定义就不会出现这个问 题,因为它是在预处理阶段即进行了宏展开,在执行时不需要转换,即在当地执行。宏定义可完成简单的操作,但复杂的操作还是要由函数调用来完成,而且宏定义所占用的目标代码空间相对较大。所以在使用时要依据具体情况来决定是否使用宏定义。

  在C或C++语言中,“宏”分为有参数和无参数两种。

  一、无参宏定义

   1. 无参宏定义的一般形式为:#define 标识符 字符串

                2.  其中的“#”表示这是一条[预处理命令](http://baike.baidu.com/view/1334643.htm)。凡是以“#”开头的均为[预处理命令](http://baike.baidu.com/view/1334643.htm)。“define”为[宏定义](http://baike.baidu.com/view/2076445.htm)命令。“[标识符](http://baike.baidu.com/view/390932.htm)”为所定义的宏名。“字符串”可以是常数、[表达式](http://baike.baidu.com/view/420676.htm)、格式串等。

[](javascript:void(0)??

 1 #include 
 2 #define    M    ( a+b )
 3 int main( int argc, char * argv[] )
 4 {
 5     int s, a, b;
 6     printf( "input number a& b: " );
 7     scanf( "%d%d", &a, &b );
 8     s = M*M;
 9     printf( "s=%d\n" ,s );
10 }

[](javascript:void(0)??

  上例程序中首先进行宏定义,定义M来替代表达式(a+b),在 s= M * M 中作了宏调用。在预处理时经宏展开后该语句变为: S=(a+b)(a+b)  但要注意的是,在宏定义中表达式(a+b)两边的括号不能少。否则会发生错误。  如当作以下定义后:#define M (a)+(b)  在宏展开时将得到下述语句:S= (a)+(b)(a)+(b)

还要说明的是:

  1.宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。

  2.宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起置换。

  3..宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令

二、带参宏定义

  c语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。

  带参宏定义的一般形式为:  #define 宏名(形参表) 字符串

  例: 

#define  M(y)  ((y)*(y)+3*(y))            /*宏定义*/
k = M(5);             /*宏调用*/

[](javascript:void(0)??

#include 
#define MAX( a, b )  ((a>b)?(a):(b))
int main( int argc, char * argv[] )
{
    int x, y, max;
    printf( "input two numbers: " );
    scanf( "%d%d", &x, &y );
    max = MAX( x, y );
    printf( "max=%d\n", max );
    return 0;
}

[](javascript:void(0)??

  上例程序的第一行进行带参宏定义,用宏名MAX表示条件表达式 (a>b)?a:b ,形参a,b均出现在条件表达式中。程序中 max=MAX(x,y) 为宏调用,实参x,y,将代换形参a,b。宏展开后该语句为: max=(x>y)?x:y;  用于计算x,y中的大数。

7.指针

7.1 概述

查看内存地址的方法:

image.png

int是4字节的,一个字节八位,可以存储两个16进制数,所以aa在一个地址里,bb在一个地址里.......

image.png

7.2 指针基础知识

#include 

int main()
{
    int a = 0xaabbccdd;
    //a =100;

    printf("%p\n", &a);
    getchar();

    return 0;
}


输出:0073F748

在基本数据类型后加上*,表示指针数据类型,如int* p

*为取值运算符,使用方法:*加上指针变量

image.png

int main()
{
    //定义指针变量存储地址
    int a = 10;
    int* p;
    p = &a;
    *p = 100;

    printf("%p\n", &a);
    printf("%p\n", p);

    printf("%d\n", a);
    printf("%d\n", *p);
    return 0;

}

输出:

009CFB5C
009CFB5C
100
100

测试指针地址占用数据内存大小:

int main()
{
    int a = 10;
    int* p = &a;
    printf("%d\n", sizeof(int*));
}

64位输出是8, ×86输出是4.

所有的指针存储的都是内存地址,内存地址都是一个无符号十六进制整型数,具体占得字节大小是由操作系统决定的。

&是取地址符号,是升维度的;*是取值符号,是降维度的

int main()
{
    char ch = 'a';
    int* p = &ch;

    printf("%p\n", p);
    printf("%p\n", &ch);

    printf("%d\n", ch);
    printf("%d\n", *p);
    return 0;
}

输出:

006FF92B
006FF92B
97
-858993567

在指针赋值时,一定要注意指针类型要和被赋值的类型一样,不然会出错

int* p 会找寻四个char* p的地址,输出就是一堆乱码了

#include 

int main()
{
    int* p;

    printf("%d\n", &p);
    printf("%p\n", &p);


    return 0;
}

输出:

17823808
010FF840

野指针:

image.png

image.png

#include 

int main()
{
    //野指针——指针变量指向一个未知的空间,程序中允许存在野指针
    int* p=100;//直接把内存地址为100的内存分配给了指针p,这个内存是否有值,是否能访问都是未知的
    //操作系统将0-255作为系统占用,不允许访问操作。

    //操作野指针对应的内存空间可能会报错
    printf("%d\n", *p);
    return 0;
}

没有任何输出,调试会报错的

空指针:

int main()
{
    //空指针是内存地址编号为0的空间
    int* p = NULL;
    //操作空指针对应的空间一定会报错

    *p = 100;//写入会报错
    printf("%d\n", *p);//读取会报错

    //空指针可以用作条件判断,if(p==NULL){} 

    return 0;
}

万能指针void:

void *指针可以指向任意变量的内存空间:

#include 

int main()
{
    int a = 10;
    //int* p = &a;
    //万能指针可以接收任意变量类型的内存地址
    void* p = &a;
    //在通过万能指针修改变量的值时,需要找到变量对应的指针类型
    //*p = 100;
    *(int*)p = 100;
    printf("%d\n", a);
    printf("%d\n", *(int*)p);

    printf("万能指针在内存中占得字节大小:%d\n", sizeof(void*));
    
    return 0;
}

输出:

100
100
万能指针在内存中占得字节大小:4

const修饰的指针变量

#include 

int main()
{
	const int a = 10;

	int* p = &a;
	*p = 100;//指针间接修改常量的值
	printf("%d\n", a);//这里可以把a的值修改成100.


	return 0;
}

输出:100

int main()
{
   int a = 10;
   int b = 20;
   const int* p = &a;

   //*p = 100;//err
   p = &b;//ok

   printf("%d\n", *p);
   return 0;
}

输出:20

int main()
{
	int a = 10;
	int b = 20;
	int* const p = &a;

	//p = &b;//err
	*p = 200;

	printf("%d\n", a);
	return 0;

}

输出:200

  1. const修饰指针类型:可以修改指针变量的值,不可以修改指针指向的内存空间
  2. const修饰指针变量:可以修改指针指向内存空间的值,不可以修改指针变量的值
int main()
{
	int a = 10;
	int b = 20;
	const int* const p = &a;
	printf("%d\n", *p);

	int** pp = &p;//二级指针,可以用于修改一级指针的值
	*pp = &b;
	printf("%d\n", *p);

	**pp = 100;
	printf("%d\n", *p);//通过二级指针也可以直接修改*p的值

	return 0;
}

输出:

10
20
100

7.3 指针和数组

数组可以理解为首地址+[]个下标地址偏移值

指针类型变量 +1 等同于内存地址+sizeof(int等指针数据类型)

#include 

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,'a','b' };
	//数组名是数组首元素的地址
	int* p = arr;

	printf("p=%p\n", p);
	printf("arr=%p\n", arr);

	*p = 123;
	for (int i = 0; i < 11; i++)
	{
		//printf("%d\n", arr[i]);
		printf("%d\t", p[i]);
	}
	printf("\n");
	for (int i = 0; i < 11; i++)
	{
		//printf("%d\n", arr[i]);
		printf("%d\t", *(arr+i));//通过地址+偏移量的方式输出数组
	}
	printf("\n");
	for (int i = 0; i < 11; i++)
	{
		//printf("%d\n", arr[i]);
		printf("%d\t", *(p+i));//通过移动指针来输出数组
	}
	putchar(10);

	printf("%d\n", *p);
	putchar(10);
	//移动指针,用赋值语法
	for (int i = 0; i < 11; i++)
	{
		//printf("%d\n", arr[i]);
		printf("%d\t", *p++);//*取值优先于自加
	}
	putchar(10);
	int step = p - arr;//两指针相减,得到的是两个指针的偏移量(步长)
	//所有的指针相减,结果都是int类型的
	printf("%d\n", step);


	return 0;
}

输出:

p=001AFB10
arr=001AFB10
123 2 3 4 5 6 7 8 9 97 98
123 2 3 4 5 6 7 8 9 97 98
123 2 3 4 5 6 7 8 9 97 98
123

123 2 3 4 5 6 7 8 9 97 98
11

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	//指向数组的指针
	int* p = arr;

	//区别:p是变量  arr是常量
	printf("指针类型大小:%d\n", sizeof(p));
	printf("数组大小:%d\n", sizeof(arr));
	//p是一个指针,4字节大小。arr是一个数组,40字节大小

	//方法一:p[i];
	//方法二:*(p+i);


	return 0;
}

输出:

指针类型大小:4
数组大小:40

不能在bubblesort里面写len=sizeof(arr)/sizeof(arr[0]),因为传参的时候会把数组传成一个一个的值,所以无论数组多大,len都等于1

void BubbleSort(int arr[],int len)//数组作为参数会退化为指针  丢失了数组的精度(元素个数),所以必须要通过传参传过来
{
	for (int i = 0; i < len-1; i++)
	{
		for (int j = 0; j < len-1-i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

int main()
{
	int arr[] = { 9,1,2,6,5,7,4,3,10,8 };
	BubbleSort(arr, 10);

	for (int i = 0; i < 10; i++)
	{
		printf("%d\t", arr[i]);
	}
	return 0;
}

输出:1 2 3 4 5 6 7 8 9 10

指针拷贝数组字符串的方法:

#include 


void my_strcpy(char* dest, char* ch)
{
	int i = 0;
	while (ch[i] != '\0')//记住是反斜杠,第一次写成/0报错了,会是无限循环,导致越界
		//可以写成while(ch[i]!=0)--->等同于while(ch[i])
	{
		dest[i] = ch[i];
		i++;
	}
	dest[i] = 0;
}
void my_strcpy2(char* dest, char* ch)
{
	int i = 0;
	while (*(ch + i))
	{
		*(dest + i) = *(ch + i);
		i++;
	}
	*(dest + i) = 0;
}
void my_strcpy3(char* dest, char* ch)
{
	while (*ch)
	{
		*dest = *ch;
		dest++;
		ch++;
	}
	dest = 0;
}
void my_strcpy4(char* dest, char* ch)
{
	while (*dest++ = *ch++);
	
}
int main()
{
	//字符串拷贝
	char ch[] = "hello world";
	char dest[100];

	my_strcpy(dest, ch);
	printf("%s\t", dest);

	return 0;
}

输出:hello world

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	int* p = arr;

	//arr[-1]  数组下标越界
	p = &arr[3];
	//指针操作数组时,下标允许是负数
	p--;//指针的加减运算与指针类型有关,如果是char就是-1了
	
	printf("%p\n", p);
	printf("%p\n", arr);
	printf("%d\n", p[-2]);
}

输出:006FFC84
006FFC7C
1

指针运算

int main()
{
	//指针和运算符的操作
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	int* p = arr;
	p = &arr[3];

	//p = p + arr;//报错,+不能添加两个指针,两个指针相加一定是野指针,加减乘除取余都不能对两个指针运算

	if (p > arr)//可以比大小
	{
		printf("真\n");
	}

	return 0;
}

输出:真

指针数组

数组中的每一个元素都是指针类型

image.png

#include 

int main()
{
	
	int a = 10;
	int b = 20;
	int c = 30;
	int* arr[3] = { &a,&b,&c };//定义指针数组
	
	printf("%d\n", *arr[0]);
	printf("指针数组大小为:%d\n", sizeof(arr));
	printf("指针元素大小为:%d\n", sizeof(arr[0]));

	return 0;
}

输出:

10
指针数组大小为:12
指针元素大小为:4

int main()
{
	//指针数组里面的的元素都是指针
	int a[] = { 1,2,3 };
	int b[] = { 4,5,6 };
	int c[] = { 7,8,9 };
	int* arr[] = { a,b,c };//指针数组相当于二级指针,因为存放的是数组的地址
	//在定义的时候可以写成int** p = arr;

	printf("%p\n", arr[0]);
	printf("%p\n",a );
	printf("%p\n", arr);//arr是指针数组的首地址
	printf("%p\n",&a[0] );

	for (int i = 0; i < 3 ; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			//用二维数组方式打印出值
			printf("%d\t", arr[i][j]);//a[i]就是数组的首地址,在后面加上[j],就是数组寻址,所以不需要加指针符号来取值
		}
		putchar(10);
	}
	//方法二:
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d\t", *(arr[i]+j));//地址加偏移量,然后*取地址的值
		}
		putchar(10);
	}
	//方法三:
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d\t", *(*(arr+i) + j));//地址加偏移量,然后*取地址的值
		}
		putchar(10);
	}

	return 0;
}

输出:

0053FDE8
0053FDE8
0053FDAC
0053FDE8
1 2 3
4 5 6
7 8 9
1 2 3
4 5 6
7 8 9
1 2 3
4 5 6
7 8 9

多级指针:**ppp*ppp==&a

7.5 指针和函数

值传递和地址传递

值传递:形参不影响实参的值

地址传递,形参可以改变实参的值,前提是形参比实参底层一级

#include


void swap(int* a, int* b)//指针作为函数参数
{
	int temp = *a;//这里主要要改变的都是值,而不是地址
	*a = *b;
	*b = temp;

}
int main()
{
	int a = 10;
	int b = 20;

	//指针作为函数参数时,传递的是地址
	swap(&a, &b);//地址传递,形参可以改变实参的值

	printf("%d\n", a);
	printf("%d\n", b);
	return 0;
}

输出:

20
10

image.png

数组名作为函数参数

#include 

//数组写法
void my_strcat(char* ch1,char* ch2)//因为字符串数组是以\0结尾的,所以不需要传递数组精度
{
	int i = 0;
	while (ch1[i]!='\0')
	{
		i++;
	}

	printf("%d\n", i);//5
	printf("%d\n", strlen(ch1));//系统提供的函数,5

	int j = 0;
	while (ch2[j] != '\0')
	{
		ch1[i + j] = ch2[j];
		j++;
	}
}

//指针写法
void my_strcat2(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_strcat3(char* ch1, char* ch2)
{
	while (*ch1)ch1++;
	while (*ch2)
	{
		*ch1 = *ch2;
		ch1++;
		ch2++;
	}//因为ch1后面100之前都是0,所以这里不需要额外加0


}
void my_strcat4(char* ch1, char* ch2)
{
	while (*ch1)ch1++;
	while (*ch1++ = *ch2++);
	

}
int main()
{
	char ch1[100] = "hello";
	char ch2[] = "world";

	my_strcat3(ch1,ch2);
	printf("%s\n", ch1);

	return 0;


}

输出:helloworld

字符串去空格:

void remove_space(char* ch)
{
	char str[100] = { 0 };
	int i = 0;
	int j = 0;
	char* temp = str;
	while (ch[i] != '\0') {
		if (ch[i] != ' ') {
			str[j] = ch[i];
			j++;
		}
		i++;
	}
	while (*ch++ = *temp++);

}

void remove_space2(char* ch)
{
	char* ftemp = ch;//用来遍历字符串
	char* rtemp = ch;//记录非空格字符串
	while (*ftemp) {
		if (*ftemp != ' ')
		{
			*rtemp = *ftemp;
			rtemp++;
		}
		ftemp++;
	}
	*rtemp = 0;
}

int main()
{
	char ch[] = " h ello   ,  worl  d ";
	remove_space2(ch);
	printf("%s\n", ch);

	return 0;
}

输出:hello,world

指针作为函数返回值

#include 

char* my_strchr1(char* ch, char l)
{
	int i = 0;
	while (ch[i])
	{
		if (ch[i] == l)
		{
			return &ch[i];
		}
		i++;
	}
	return NULL;
}

char* my_strchr2(char* ch, char l)
{
	while (*ch)
	{
		if (*ch == l)
		{
			return ch;
			ch++;
		}
		return NULL;
	}
}
int main()
{
	char ch[] = "helloworld";
	char* p = my_strchr2(ch, 'm');
	if (p == NULL)
	{
		printf("未找到");
	}
	else
	{
		printf("%s\n", p);
	}

	return 0;
}

输出:未找到

查找字符串

#include 

char* my_strstr(char* src,char* dest )
{
	char* fsrc = src;//定义遍历原字符串的指针
	char* rsrc = src;//记录查找到的字符串的首地址
	char* tdest = dest;

	while (*fsrc)
	{
		rsrc = fsrc;
		while (*fsrc == *tdest&&*fsrc!='\0')//如果是最后几个字符相同,则需要后面的条件判断,否则判断\0会导致数组下标越界
		{
			fsrc++;
			tdest++;
		}
		if (*tdest=='\0')
		{
			return rsrc;
		}
		//如果不一样,则需要目标指针回指第一个字符
		tdest = dest;
		fsrc = rsrc;
		fsrc++;
	}
	return NULL;
}

int main()
{
	char src[] = "hello world";
	char dest[] = "llo";
	char* p = my_strstr(src, dest);
	printf("%s\n", p);

	
	return 0;
}

输出:llo world

7.6 指针和字符串

7.6.1 字符指针

#include 

int main()
{
	char ch[] = "hello world";//栈区字符串,可以被用下标和指针方式修改
	char* p = "hello world";//数据区常量区字符串,不可以被修改
	char* p1 = "hello world";

	//ch[2]='m'; 是可以通过的,修改了ch[]中的值
	//p[2]或者是*(p+2)='m',是会报错的。无法写入

	printf("%p\n", p);
	printf("%p\n", p1);//因为是只读的,所以一样的内容只需要保存一次,多个指针可以指向同一个地址

	return 0;
}

输出:

00897B30
00897B30

int main()
{
	//字符串数组,指针数组
	char ch1[] = "hello";
	char ch2[] = "world";
	char ch3[] = "dabaobei";
	char* arr[] = { ch1,ch2,ch3 };//可以被修改
	//字符串数组
	char* arr[] = { "hello","world","dabaobei" };//这是存储在常量池里的,不能被修改

	return 0;
}

7.6.2字符指针作函数参数

#include

int my_strlen(ch)
{
	char* temp = ch;
	while (*temp != '\0')temp++;
	return temp - ch;
}

int main()
{
	char ch[] = "hello world";
	int len = my_strlen(ch);
	printf("%d\n", len);

	return 0;
}

输出:11

7.6.3 const修饰的指针变量

#include 

int main()
{
	char ch1[] = "hello";
	char ch2[] = "world";
	const char* p = ch1;
	//指向常量的指针
	//可以修改指针变量的值,不可以修改指针变量指向内存空间的值

	p = ch2;
	for (int i = 0; i < 5; i++)
	{
		printf("%c", *(p+i));
	}
	

	return 0;
}

输出:world

int main()
{
	char ch1[] = "hello";
	char ch2[] = "world";
	char* const p = ch1;
	//可以修改指针变量指向空间的值,不可以修改指针变量的值

	p[2] = 'm';
	*(p + 1) = 'a';

	printf("%s\n", p);
	printf("%s\n", ch1);

	return 0;
}

输出:

hamlo
hamlo

7.6.4 主函数参数

#include 

int main(int argc,char* argv[])//argc表示传递参数的个数,argv用来保存传递的字符串
{
	for (int i = 0; i < argc; i++)
	{
		printf("%s\n", argv[i]); 
	}


	return 0;
}

gcc编译不通过先检查下语法,然后看下是不是写完然后保存了。

image.png

7.6.5 常用字符串应用模型

#include

int strlen(ch)
{
	char* temp = ch;
	while (*temp != '\0')temp++;
	return temp - ch;
}

int main()
{
	char ch[] = "dasjfdsfn dsancixjzicnmcnekwlj kladsdkfksfnkdasnfkljdsalkfndsa";

	int arr[26] = { 0 };//存储字符串出现的个数

	for (int i = 0; i < strlen(ch); i++)
	{
		arr[ch[i] - 'a']++;//0-26每一个字母遇到,在arr数组里就给相对应的数字+1
	}

	for (int i = 0; i < 26; i++)
	{
		if (arr[i])
		{
			printf("字母%c出现的次数为:%d\n", 'a' + i, arr[i]);
		}

	}
	return 0;

}

输出:字母a出现的次数为:6
字母c出现的次数为:3
字母d出现的次数为:8
字母e出现的次数为:1
字母f出现的次数为:6
字母i出现的次数为:2
字母j出现的次数为:4
字母k出现的次数为:7
字母l出现的次数为:4
字母m出现的次数为:1
字母n出现的次数为:7
字母s出现的次数为:8
字母w出现的次数为:1
字母x出现的次数为:1
字母z出现的次数为:1

字符串逆置:

#include

void inverse(char* ch)
{
	int i = 0;
	int j = strlen(ch) - 1;
	while (i < j)
	{
		char temp = ch[j];
		ch[j] = ch[i];
		ch[i] = temp;
		i++;
		j--;
	}
	return;
}

void inverse2(char* ch)
{
	char* ftemp = ch;
	char* btemp = ch + strlen(ch) - 1;
	while (ftemp

输出:dlrow,olleh

回文字符串:

//回文字符串(对称的字符串),如abccba

int symm(char* ch)
{
	char* ftemp = ch;
	char* btemp = ch + strlen(ch) - 1;

	while (ftemp

输出:是回文字符串

7.6.6 字符串处理函数

需要导入string.h头文件

  1. strcpy():char* strcpy(char* dest, const char *src)

    把src所指向的字符串复制到dest所指向的空间中,\0也会被烤进去。如果参数dest所指向的内存空间不够大,可能会造成缓冲溢出的错误情况,是一个不安全的行为,需要在头文件#define一下

    int main()
    {
    	char ch[] = "helloworld";
    
    	char str[100];
    
    	strcpy(str, ch);
    
    	printf("%s\n", str);
    
    	return 0;
    }
    

    输出:helloworld

  2. strncpy(): char* strncpy(char* dest , const char *src,size_t n);

    把src指向的字符串前n个字符复制到dest所指向的空间,是否拷贝结束看的是指定长度是否包含'\0';

    #include
    
    int main()
    {
    	char ch[] = "helloworld";
    
    	char str[100] = { 0 };//这里如果不初始化,拷贝的字符串不到含有\0,会有乱码
    
    	//strcpy(str, ch);//字符串拷贝
    	strncpy(str, ch, 5);
    
    	printf("%s\n", str);
    
    	return 0;
    }
    

    输出:hello

  3. strcat() : char* strcat(char* dest,const char* src);功能是将src字符串连接到dest的尾部。

    #include
    
    void my_strcat(char* dest, char* src)
    {
    	while (*dest)dest++;
    	while (*dest++ = *src++);
    }
    
    int main()
    {
    	char dest[100] = "hello";
    	char src[] = "world";
    
    	my_strcat(dest, src);
    
    	printf("%s\n", dest);
    
    	return 0;
    }
    

    输出:helloworld

  4. strncat() : char* strncat(char* dest, const char* src, size_t n);

    功能:将src字符串前n个字符连接到dest 的尾部,‘\0’也会被追加过去。

    实现方法:

    void my_strncat(char* dest, char* src, int n)
    {
    	while (*dest)dest++;
    	while ((*dest++ = *src++)&&--n);
    }
    
  5. strcmp() : int strcmp(const char* s1,const char* s2)

    功能:比较s1,s2的大小,比较的是ASCII码的大小。相等:0,大于:>0,小于:<0.返回的是ASCII码的差值,或者-1,1

  6. strncmp() : int strncmp(const char* s1,const char* s2,size_t n) : 比较前n个字符的大小

    #include
    
    int main()
    {
    	char ch1[] = "hello world";
    	char ch2[] = "hello world";
    
    	int value = strcmp(ch1, ch2);
    	printf("%d\n", value);
    
    	return 0;
    }
    

    输出:0

  7. sprintf() : 导入stdio库,Int sprintf(char *str, const char *format,...) : 功能 :根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到出现字符串结束符'\0'为止。

    format是字符串格式,跟printf一样。

    #define _CRT_SECURE_NO_WARNINGS
    #include
    
    int main()
    {
    	char ch[100];
    
    	sprintf(ch, "hello world");
    	sprintf(ch, "%02d+%02d=%02d", 1, 2, 3);
    	sprintf(ch, "%x+%o=%d", 1234, 2312, 3);
    
    	printf("%s\n", ch);
    
    	return 0;
    }
    

    输出:4d2+4410=3

  8. sscanf : int sccanf(const char * str,const char* format,...);

    功能:从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。sscanf(src,"a=%d,b=%d,&a,&b);

  9. strchr() : string.h库,在字符串s中查找字母c出现的位置

    char* strchr(const char* s,int c);

    #define _CRT_SECURE_NO_WARNINGS
    #include
    
    int main()
    {
    	char ch[] = "helloworld";
    	char c = 'l';
    	char* p = strchr(ch, c);
    	printf("%s\n", p);
    
    	return 0;
    }
    

    输出:lloworld

  10. strstr():省略

  11. strtok : 功能是将字符串分割成一个个片段。当strtok()在参数s的字符串中发现参数delim中包含的分割字符时,则会将该字符改为0字符,当连续出现多个时只替换第一个为\0。

    char* strtok(char str,const char delim)

    #define _CRT_SECURE_NO_WARNINGS
    #include
    
    int main()
    {
    	char ch[] = "www.baidu.com";//strtok字符串截取会破坏原字符串,会用\0替换分隔的标志位
    	//www\0baidu.com
    
    	char* p = strtok(ch, ".");
    
    	printf("%s\n", p);
    	printf("%s\n", ch);
    	printf("%p\n", p);
    	printf("%p\n", ch);
    
    	p = strtok(NULL, ".");//www\0baidu\0com
    	printf("%s\n", p);
    
    	p = strtok(NULL, ".");
    	printf("%s\n", p);
    
    	return 0;
    }
    

    输出:

    www
    www
    007BF7B0
    007BF7B0
    baidu
    com

  12. atoi() : stdlib.h库,

    int atoi(const char* nptr);

    功能:atoi()会扫描nptr字符串,跳过前面的空格字符,直到遇到数字或正负号才开始做转换,而遇到非数字或者字符串结束符(‘\0’)才会结束转换,并将结果返回返回值。

    atof()转化成浮点型,atol()转换成long类型

    #define _CRT_SECURE_NO_WARNINGS
    #include
    #include
    
    
    int main()
    {
    	char ch[] = "    -123456abc123";
    
    	int i = atoi(ch);
    	printf("%d\n", i);
    
    	return 0;
    
    }
    

    输出:-123456