学习笔记之13-指针和字符串

字符串回顾

一个字符串由一个或多个字符组成,因此我们可以用字符数组来存放字符串,不过在数组的尾部要加上一个空字符‘\0‘。

char s[] = "mj";

上面的代码定义了一个字符数组s来存储字符串"mj",系统会自动在尾部加上一个空字符‘\0‘。

内存分布大致如右图所示:

指针和数组的关系非常密切,因此也可以使用指针来操作字符串。

一、用指针遍历字符串的所有字符

 1 // 定义一个指针p
 2 char *p;
 3
 4 // 定义一个数组s存放字符串
 5 char s[] = "mj";
 6
 7 // 指针p指向字符串的首字符‘m‘
 8 p = s; // 或者 p = &s[0];
 9
10 for (; *p != ‘\0‘; p++) {
11     printf("%c \n", *p);
12 }

执行完第8行后,内存分布如右图:

有了前面指针与数组的基础相信大家能看到第10行之后的代码了:每次遍历之前先判断p当前指向的字符是否为空字符\0,如果不是空字符,就打印当前字符,然后执行p++让指针p指向下一个字符元素。

最后的输出结果:

二、用指针直接指向字符串

从前面可以看出,指针确实可以指向字符串并操作字符串。不过前面的做法是:先定义一个字符串数组存放字符串,然后将数组首地址传给指针p,让p指向字符串的首字符。

1.我们也可以直接用指针指向一个字符串,省略定义字符数组这个步骤

 1 #include <string.h>
 2
 3 int main()
 4 {
 5     // 定义一个字符串,用指针s指向这个字符串
 6     char *s = "mj";
 7
 8     // 使用strlen函数测量字符串长度
 9     int len = strlen(s);
10
11     printf("字符串长度:%D", len);
12     return 0;
13 }

注意第6行,我们直接用指针s指向了字符串"mj",并没有先创建一个字符数组。看第9行,将指针s传入到strlen函数中,说明之前所学习的字符串处理函数依然可以正常使用。输出结果:

2.我们再来看看strlen函数在string.h中的声明

size_t     strlen(const char *);

strlen函数中的形参是指向字符变量的指针类型,在《10-字符和字符串常用处理函数》中可以将一个字符数组名传进去,这一点又说明了指针与数组的密切关系,肯定有JQ。其实,调用strlen函数时,传一个地址给它就行了,它会从这个地址开始计算字符的个数,直到遇到空字符‘\0‘位置,因此传入指针变量或者数组名都可以。

其他字符串处理函数也是一样的:

1 char    *strcpy(char *, const char *); // 字符串拷贝函数
2 char    *strcat(char *, const char *); // 字符串拼接函数
3 int     strcmp(const char *, const char *); // 字符串比较函数

它们的参数都是指向字符变量的指针类型,因此可以传入指针变量或者数组名。

因此printf函数依然可以正常使用:

char *s = "mj";
printf("%s", s);

输出结果:

3.指针指向字符串的其他方式

char *s;
s = "mj";

上面的指向方式也是正确的:先定义指针变量,再指向字符串。如果是字符数组就不允许这样做,下面的做法是错误的:

1 char s[10];
2 s = "mj";

编译器肯定报第2行的错,因为s是个常量,代表数组的首地址,不能进行赋值运算。

还需要注意的是,下面的做法也是错误的:

1 char *s = "mj";
2
3 *s = "like";

第3行代码犯了2个错误:

  • 第3行代码相当于把字符串"like"存进s指向的那一块内存空间,由第1行代码可以看出,s指向的是"mj"的首字符‘m‘,也就是说s指向的一块char类型的存储空间,只有1个字节,要"like"存进1个字节的空间内,肯定内存溢出
  • 由第1行代码可以看出,指针s指向的是字符串常量"mj"!因此是不能再通过指针来修改字符串内容的!就算是*s = ‘A‘这样"看起来似乎正确"的写法也是错误的,因为s指向的一个常量字符串,不允许修改它内部的字符。

三、指针处理字符串的注意

现在想将字符串"lmj"的首字符‘l‘改为‘L‘,解决方案是多种的

1.第一种方案

1 // 定义一个字符串变量"lmj"
2 char a[] = "lmj";
3
4 // 将字符串的首字符改为‘L‘
5 *a = ‘L‘;
6
7 printf("%s", a);

程序正常运行,输出结果:

2.马上想到第二种方案

1 char *p2 = "lmj";
2 *p2 = ‘L‘;
3
4 printf("%s", p2);

看起来似乎是可行的,但这是错误代码,错在第2行。首先看第1行,指针变量p2指向的是一块字符串常量,正因为是常量,所以它内部的字符是不允许修改的。

有人可能搞蒙了,这里的第1行代码char *p2 = "lmj";跟第一种方案中的第2行代码char a[] = "lmj";不是一样的么?这是不一样的。

  • char a[] = "lmj";定义的是一个字符串变量!
  • char *p2 = "lmj";定义的是一个字符串常量!
时间: 2024-08-26 12:31:54

学习笔记之13-指针和字符串的相关文章

【Qt学习笔记】13.拖放技术:Drag & Drop

1.接受拖放 Drag & Drop 是一个界面操作,用于在两个窗口间传递数据. Drag Source: 拖放源窗口 Drag Target: 拖放目标窗口 拖放操作: 1.在源窗口:选中目标,按下鼠标,移动,拖至目标窗口(Drag) 2.在目标窗口:取消鼠标,到指定位置,松开鼠标(Drop) (按下ESC取消操作) MIME: MIME(Multipurpose Internet Mail Extensions)被传递的数据以MIME格式传送,它是多组type-data数据:(type0,

C++学习笔记之this指针

为了说明这个问题,首先来建立一个简单的类 1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 class Book 7 { 8 private: 9 string title; 10 int year; 11 double price; 12 public: 13 double getPrice() const { return price;} 14 } 这个类是关于书籍信息的简单表示,它可以

C++学习笔记之函数指针

与数据项类似,函数也有地址.函数的地址是存储其机器语言代码的内存开始的地方. 一.函数指针的基础知识 假设要设计一个名为estimate()的函数,估算编写指定行数代码所需时间,并且希望不同的程序员都使用该函数,并且该函数允许每个程序员提供自己的算法来估计时间.为实现这种目标,采用的机制是,将程序员要使用的算法函数地址传给estimate(),必须完成以下工作: 获取函数地址 声明一个函数指针 用函数指针来调用函数 1.获取函数地址 使用函数名(后面不跟参数)即可.如:think()是一个函数,

《Javascript权威指南》学习笔记之十一:处理字符串---String类和正则表达式

一.正则表达式的基本语法 1.概念:正则表达式由普通字符和特殊字符(元字符)组成的文本模式,该模式描述在查找字符串主体时待匹配的一个或者多个字符串.正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配. 普通字符包括所有的大小写字母字符.所有数字.所有标点符号及一些特殊符号.普通字符本身可以组成一个正则表达式,也可以和元字符组合组成一个正则表达式:而元字符则具有特殊的含义,包括().[].{}./.^.$.*.+.?...|.-.?:.?=.?! 2.基本语法 3.优先权含义 二.使用

Guava学习笔记:guava中对字符串的操作

Guava学习笔记:guava中对字符串的操作 转载:http://outofmemory.cn/java/guava/base/Strings 在google guava中为字符串操作提供了很大的便利,有老牌的判断字符串是否为空字符串或者为null,用指定字符填充字符串,以及拆分合并字符串,字符串匹配的判断等等. 下面我们逐一了解这些操作: 1. 使用com.google.common.base.Strings类的isNullOrEmpty(input)方法判断字符串是否为空        

C++学习笔记30,指针的引用(2)

可以创建任何类型的引用,包括指针类型. 看一个简单的指针的引用的例子.例如: #include <iostream> using namespace std; int main(){ int x=10; int y=20; int z=30; int* ptx=&x; int* ptz=&z; //指针的引用,声明从右往左看,rtp与&结合, //剩余的符号和左边结合 //引用一旦创建,不能改变其指向,只能改变其值 int* &rtp=ptx; cout<

java/android 设计模式学习笔记(13)---享元模式

这篇我们来介绍一下享元模式(Flyweight Pattern),Flyweight 代表轻量级的意思,享元模式是对象池的一种实现.享元模式用来尽可能减少内存使用量,它适合用于可能存在大量重复对象的场景,缓存可共享的对象,来达到对象共享和避免创建过多对象的效果,这样一来就可以提升性能,避免内存移除和频繁 GC 等. 享元模式的一个经典使用案例是文本系统中图形显示所用的数据结构,一个文本系统能够显示的字符种类就是那么几十上百个,那么就定义这么些基础字符对象,存储每个字符的显示外形和其他的格式化数据

Swift学习笔记(4):字符串

目录: 初始化 常用方法或属性 字符串索引 初始化 创建一个空字符串作为初始值: var emptyString = "" // 空字符串字面量 var anotherEmptyString = String() // 初始化方法,两个字符串均为空并等价. 常用方法或属性 1 var empty = emptyString.isEmpty // 判断字符串是否为空 2 var welcome = "string1" + string2 // 使用 + 或 += 拼接

UNIX环境编程学习笔记(13)——文件I/O之标准I/O流

lienhua342014-09-29 1 标准 I/O 流 之前学习的都是不带缓冲的 I/O 操作函数,直接针对文件描述符的,每调用一次函数可能都会触发一次系统调用,单次调用可能比较快捷.但是,对于需要频繁进行 I/O 操作的程序,频繁触发系统调用产生的消耗太大. 标准 I/O 库提供了带缓冲的 I/O 操作函数,这些函数围绕着一种叫做流(stream)的东西进行.当使用标准 I/O 库打开或创建一个文件时,系统提供了一个流与这个文件相关联.通过流的读入和输出完成所需要的 I/O操作. 标准