C++笔记day8-字符串和内存

147 阅读5分钟

求非空字符串元素个数:

“ni chou sha chou ni za di”  

示例

int no_spa_str(char* str)
{
	int count = 0;
	while (*str) 
	{
		if (*str != ' ') 
		{
			count++;
		}
		str++;
	}
	return count;
}

int main(void)
{
	char str[] = "ni chou sha chou ni za di";
	int ret = no_spa_str(str);
	printf("ret = %d\n", ret);

	system("pause");
	return EXIT_SUCCESS;
}

字符串逆置: str_inverse

hello -- olleh 
void str_inserse(char *str)
{
	char *start = str;			// 记录首元素地址
	char *end = str + strlen(str) - 1;	// 记录最后一个元素地址。

	while (start < end)			// 首元素地址是否 < 最后一个元素地址
	{
		char tmp = *start;		// 三杯水 char 元素交换
		*start = *end;
		*end = tmp;
		start++;			// 首元素对应指针后移
		end--;				// 尾元素对应指针前移
	}
}

示例


void arr_inverse (int *arr,int n) //整型数组逆序
{
	int i = 0;
	int j = n - 1;
	int temp = 0;
	while (i < j) 
	{
		temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
		i++;
		j--;
	}
}
void str_inverse(char* str) //字符串逆序
{
	char* start = str; //记录首元素地址;
	char* end = str + strlen(str) - 1;//记录最后一个元素地址;
	while (start < end)
	{
		char temp = *start;
		*start = *end;
		*end = temp;
		start++; //首元素对应指针后移;
		end--; //尾元素对应指针前移;
	}
}

int main(void)
{
	int arr[] = { 23,25,45,89,11 };
	int n = sizeof(arr)/sizeof(arr[0]);
	char str[] = "ni chou sha";

	arr_inverse(arr, n); //整型数组逆序;
	str_inverse(str); //字符串逆置;
	for (size_t i = 0; i < n; i++) //打印逆序后整型数组;
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	printf("%s\n", str); //打印逆序后字符串;
	system("pause");
	return EXIT_SUCCESS;
}

判断字符串是回文:

int str_abcbb(char *str)
{
	char *start = str;			// 记录首元素地址
	char *end = str + strlen(str) - 1;	// 记录最后一个元素地址。

	while (start < end)			// 首元素地址是否 < 最后一个元素地址
	{
		if (*start != *end)		// 判断字符是否一致。
		{
			return 0; 		// 0 表示非 回文
		}
		start++;
		end--;
	}
	return 1;				// 1 表示 回文
}

示例

int str_abcba(char* str) 
{
	char* start = str;
	char* end = str + strlen(str) - 1;
	while (start < end) 
	{
		if (*start == *end) 
		{
			start++;
			end--;
		}
		else 
		{
			return 0;
		}
	}
	return 1;
}

int main(void)
{
	char str[] = "hellolleh";
	int ret = str_abcba(str);
	if (ret == 1) 
	{
		printf("是回文\n");
	}else
	{
		printf("不是回文\n");
	}
	system("pause");
	return EXIT_SUCCESS;
}

#字符串处理函数: #include <string.h>

字符串拷贝:

strcpy: 

将 src 的内容,拷贝给 dest。 返回 dest。 保证dest空间足够大。【不安全】

    char *strcpy(char *dest, const char *src);

    函数调用结束 返回值和 dest参数结果一致。

strncpy:

    将 src 的内容,拷贝给 dest。只拷贝 n 个字节。 通常 n 与dest对应的空间一致。 

    默认 不添加 ‘\0char *strncpy(char *dest, const char *src, size_t n);	

    特性: n > src: 只拷贝 src 的大小

   n < src: 只拷贝 n 字节大小。 不添加 ‘\0

示例

int main(void)
{
	char src [] = "hello world";
	char dest[100] = {0};

	char *p =strcpy(dest, src);
	printf("dest = %s\n", dest);
	printf("p=%s\n", p);
        
	char dest1[100] = { 0 };
	char* p1 = strncpy(dest1, src, 9);
        
	printf("dest = %s\n", dest1);
	printf("p=%s\n", p1);

	system("pause");
	return EXIT_SUCCESS;
}

字符串拼接:

strcat:

    将 src 的内容,拼接到 dest 后。 返回拼接后的字符串。	保证 dest 空间足够大。

    char *strcat(char *dest, const char *src);

strncat:

    将 src 的前 n 个字符,拼接到 dest 后。 形成一个新的字符串。保证 dest 空间足够大。

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

    函数调用结束 返回值和 dest 参数结果一致。

示例

int main(void)
{
	char src[] = "hello world";
	char dest[] = "we are saying ";

	char* p = strcat(dest, src);
	printf("dest = %s\n", dest);
	printf("p=%s\n", p);
        
	char dest1[] = "we are saying ";
	char* p1 = strncat(dest1, src, 5);
        
	printf("dest = %s\n", dest1);
	printf("p=%s\n", p1);
        
	system("pause");
	return EXIT_SUCCESS;
}

字符串比较: 不能使用 > < >= <= == !=

strcmp:

    比较s1和s2两个字符串,如果相等 返回0.如果不相等,进一步表 s1 和 s2 对应位 ASCII码值。

    s1 > s2 返回1

    s1 < s2 返回-1

    int strcmp(const char *s1, const char *s2);

strncmp:

    int strncmp(const char *s1, const char *s2, size_t n);

    比较s1和s2两个字符串的前n个字符,

    如果相等 返回0。如果不相等,进一步表 s1 和 s2 对应位 ASCII码值。(不比字符串ASCII码的和)

    s1 > s2 返回1

    s1 < s2 返回-1

示例

int main(void) //字符串比较
{
	char *str1 = "helloworld";
	char *str2 = "hello";

	printf("ret = %d\n", strcmp(str1, str2));
	printf("ret2 = %d\n", strncmp(str1, str2,5));

	system("pause");
	return EXIT_SUCCESS;
}

字符串格式化输入、输出:

sprintf():  s -- string

    int sprintf(char *str, const char *format, ...);

    对应printf,将原来写到屏幕的“格式化字符串”,写到 参数1 str中。

    printf("%d+%d=%d\n", 10, 24, 10+24);

    ---》 
    char str[100];

    sprintf(str, "%d+%d=%d\n", 10, 24, 10+24);  格式串写入str数组中。

sscanf():

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

    对应scanf, 将原来从屏幕获取的“格式化字符串”, 从 参数1 str中 获取。

    scanf("%d+%d=%d", &a, &b, &c);

    ---》

    char str[]= "10+24=45";

    sscanf(str, "%d+%d=%d", &a, &b, &c);  
    a --> 10, b --> 24, c --> 45

示例

int main(void)
{
	char buf[100] = { 0 };
	sprintf(buf, "%d%c%d=%d\n", 10, '+', 34, 10 + 34);
	printf(buf);

	int a, b, c;
//	scanf("%d+%d=%d", &a, &b, &c);
	char str[] = "13+25=58";
	sscanf(str,"%d+%d=%d", &a, &b, &c);
	printf("a=%d\n", a);
	printf("b=%d\n", b);
	printf("c=%d\n",  c);

	system("pause");
	return EXIT_SUCCESS;
}

字符串查找字符、子串:

strchr():

    在字符串str中 找一个字符出现的位置。 返回字符在字符串中的地址。

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

    printf("%s\n" strchr("hehehahahoho", 'a'));  --> "ahahoho"

strrchr():

    自右向左,在字符串str中 找一个字符出现的位置。 返回字符在字符串中的地址。

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

    printf("%s\n" strrchr("hehehahahoho", 'a'));  --> "ahoho"

strstr():

    在字符串str中,找子串substr第一次出现的位置。返回地址。

    char *strstr(const char *str, const char *substr);

    在字符串中找子串的位置。 

    printf("%s\n" strrchr("hehehahahoho", "ho"));  --> "hoho"

    printf("%s\n" strrchr("hehehahahoho", "xixi"));  --> NULL

字符串分割:

scanf("%s", str);

scanf("%[^\n]", str);
实际工作中基本上字符串分割用的更多的是正则表达式;


strtok(): 
    按照既定的分割符,来拆分字符串。“www.baidu.com”  --> "www\0baidu.com"

char *strtok(char *str, const char *delim);

    参1: 待拆分字符串

    参2: 分割符组成的“分割串”

    返回:字符串拆分后的首地址。 “拆分”:将分割字符用 '\0'替换。

特性:
    1)strtok拆分字符串是直接在 原串 上操作,所以要求参1必须,可读可写(char *str = "www.baidu.com" 不行!!!)

    2)第一次拆分,参1 传待拆分的原串。	第1+ 次拆分时,参1传 NULL.

示例

int main(void)
{
	char str[] = "www.itcast.cn.com.net";//www itcast cn 

	char* p = strtok(str, "."); //第一次拆分,参1 传待拆分的字符串
	
	while (p != NULL)
	{
		p = strtok(NULL, "."); //第1+次拆分时,参1传NULL,会自动链接到拆分后串的地址
		printf("%s\n", p);
	}

	system("pause");
	return EXIT_SUCCESS;
}

练习: 拆分 ".itcast.cnThisisastrtokThis is a strtoktest"

	char str[] = "www.itcast.cn$This is a strtok$test";

	char *p = strtok(str, "$ .");

	while (p != NULL)
	{	
		p = strtok(NULL, " .$");
		printf("p = %s\n", p);
	}

字符串转换其他数据类型:atoi/atof/atol:

	使用这类函数进行转换,要求,原串必须是可转换的字符串。

	错误使用:"abc123" --> 0;	"12abc345" ---> 12;  "123xyz" -->123
	
	atoi:字符串 转 整数。

	int atoi(const char *nptr);

	atof:字符串 转 浮点数

	atol:字符串 转 长整数

示例

int main(void)
{
	char str1[] = "10";
	int num1 = atoi(str1);
	printf("num1 = %d\n", num1);

	char str2[] = "0.321";
	double num2 = atof(str2);
	printf("num2 = %f\n", num2);

	char str3[] = "1010L";
	int num3 = atol(str3);
	printf("num3 = %ld\n", num3);

	system("pause");
	return EXIT_SUCCESS;
}


局部变量:

概念:定义在函数内部的变量。

作用域:从定义位置开始,到包裹该变量的第一个右大括号结束。

全局变量:

概念:定义在函数外部的变量。

作用域:从定义位置开始,默认到本文件内部。 其他文件如果想使用,可以通过声明方式将作用域导出。

static全局变量:

定义语法: 在全局变量定义之前添加 static 关键字。		static int a = 10;

作用域:被限制在本文件内部,不允许通过声明导出到其他文件。

static局部变量:

定义语法: 在局部变量定义之前添加 static 关键字。

特性: 静态局部变量只定义一次。在全局位置。 通常用来做计数器。

作用域:从定义位置开始,到包裹该变量的第一个右大括号结束。

示例

void test(void) 
{
	static int b = 1; //相当于b是定义在全局位置,只定义一次;但是作用域只是在函数内;
	printf("b = %d\n", b++); //局部静态变量可以被操作;b = 1,..10等,可以作为计数器;
}

int main(void)
{
	for (size_t i = 0; i < 10; i++)
	{
		test();
	}
	system("pause");
	return EXIT_SUCCESS;
}

全局函数: 函数

定义语法: 函数原型 + 函数体

static函数:

定义语法:static + 函数原型 + 函数体

static 函数 只能在 本文件内部使用。 其他文件即使声明也无效。

生命周期:

局部变量:从变量定义开始,函数调用完成。 --- 函数内部。

全局变量:程序启动开始,程序终止结束。  --- 程序执行期间。

static局部变量:程序启动开始,程序终止结束。  --- 程序执行期间。

static全局变量:程序启动开始,程序终止结束。  --- 程序执行期间。

全局函数:程序启动开始,程序终止结束。  --- 程序执行期间。

static函数:程序启动开始,程序终止结束。  --- 程序执行期间。

当全局变量和局部变量重名的时候,采用就近原则

内存4区模型:

代码段:.text段。 程序源代码(二进制形式)。

数据段:只读数据段 .rodata段。初始化数据段 .data段。 未初始化数据段 .bss 段。

stack:栈。 在其之上开辟 栈帧。	windows 1M --- 10M	Linux: 8M --- 16M

heap:堆。 给用户自定义数据提供空间。 约 1.3G+

内存4区模型.png

开辟释放 heap 空间:

void *malloc(size_t size);  申请 size 大小的空间

	返回实际申请到的内存空间首地址。 【我们通常拿来当数组用】

void free(void *ptr);	释放申请的空间

	参数: malloc返回的地址值。

示例

int main(void)
{
	int *p=(int *)malloc(sizeof(int) * 10);//申请malloc空间
        //char *str = (char *)malloc(sizeof(char)*10);
	if (p == NULL) //如果申请失败提示
	{
		printf("malloc error\n");
		return -1;
	}
        int * tmp = p;//记录malloc返回的地址值,用于free操作;
	//写数据到malloc空间;
	for (size_t i = 0; i < 10; i++)
	{
		p[i] = i + 10;
	}
	//
	for (size_t i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	//释放申请的内存
	free(tmp);
        //如果有malloc之后的地址p一定会变化如有p++操作
        //提前使用临时变量tmp保存一份用于释放;
        tmp =NULL;//通常将free后的地址设为NULL;
	system("pause");
	return EXIT_SUCCESS;
}

使用 heap 空间:

空间时连续。 当成数组使用。

free后的空间,不会立即失效。 通常将free后的 地址置为NULLfree 地址必须 是 malloc申请地址。否则出错。

如果malloc之后的地址一定会变化,那么使用临时变量tmp 保存。

二级指针对应的 heap空间:

申请外层指针: 
    char **p = (char **)malloc(sizeof(char *) * 5);

申请内层指针: 
    for(i = 0; i < 5; i++)
    {
        p[i] = (char *)malloc(sizeof(char) *10);
    }

使用: 不能修改 p 的值。

     for(i = 0; i < 5; i++)
    {
        strcpy(p[i], "helloheap");
    }

释放内层:

     for(i = 0; i < 5; i++)
    {
        free(p[i]);
    }

释放外层:

    free(p);

示例

int main(void)
{
	//申请空间;先申请外层
	int** p = malloc(sizeof(int*) * 3);// int **p ==> int *p[3];
	//申请内层空间;
	for (size_t i = 0; i < 3; i++)
	{
		p[i] = malloc(sizeof(int) * 5);
	}
	//使用空间-写;
	for (size_t i = 0; i < 3; i++)
	{
		for (size_t j = 0; j < 5; j++) 
		{
			p[i][j] = i + j;
		}
	}
	//使用空间-读;
	for (size_t i = 0; i < 3; i++)
	{
		for (size_t j = 0; j < 5; j++)
		{
			printf("%d ", *(*(p + i) + j));//p[i][j] ==*(p*i)[j] ==*(*(p+i)+j);
		}
	}
	//释放空间;先释放内层空间
	for (size_t i = 0; i < 3; i++)
	{
		free(p[i]);
		p[i] = NULL;//不做也无所谓;
	}
	//释放内层空间
	free(p);
	p = NULL;

	system("pause");
	return EXIT_SUCCESS;
}

栈的存储特性:

局部变量:

形参:在定义函数时指定的形参,在未出现函数调用时,它们并不占内存中的存储单元,因此称它们是形式参数或虚拟参数,简称形参,表示它们并不是实际存在的数据,所以,形参里的变量不能赋值。

内存操作函数:

memset:

#include <string.h>

void *memset(void *s, int c, size_t n);

功能:将s的内存区域的前n个字节以参数c填入

参数:
    s:需要操作内存s的首地址
    c:填充的字符,c虽然参数为int,但必须是unsigned char , 范围为0~255
    n:指定需要设置的大小 
    返回值:s的首地址

示例

       int a[10];
       memset(a, 0, sizeof(a));

       memset(a, 97, sizeof(a));

       int i = 0;

       for (i = 0; i < 10; i++)

       {

              printf("%c\n", a[i]);

       }

memcpy和memmove:

#include <string.h>

void *memcpy(void *dest, const void *src, size_t n);

功能:拷贝src所指的内存内容的前n个字节到dest所值的内存地址上。

参数:
    dest:目的内存首地址
    src:源内存首地址,注意:dest和src所指的内存空间不可重叠,可能会导致程序报错
    n:需要拷贝的字节数
    返回值:dest的首地址

示例

   int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

   int b[10];

  

   memcpy(b, a, sizeof(a));

   int i = 0;

   for (i = 0; i < 10; i++)

   {

      printf("%d, ", b[i]);

   }

   printf("\n");

 

   //memcpy(&a[3], a, 5 * sizeof(int)); //err, 内存重叠
memmove
    memmove()功能用法和memcpy()一样。
    区别在于:dest和src所指的内存空间重叠时,memmove()仍然能处理,不过执行效率比memcpy()低些。

memcmp:

#include <string.h>

int memcmp(const void *s1, const void *s2, size_t n);

功能:比较s1和s2所指向内存区域的前n个字节

参数:
    s1:内存首地址1
    s2:内存首地址2
    n:需比较的前n个字节
返回值:
    相等:=0
    大于:>0
    小于:<0

示例

       int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
       int b[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
       
       int flag = *memcmp*(a, b, sizeof(a));

       *printf*("flag = %d\n", flag);

内存常见问题:

1) 申请 0 字节空间

2free空指针

3)越界访问

4free ++后的地址

5)子函数malloc空间,main中用