本篇博客是对educoder相关实训知识的汇总
指针是 C 语言中的一个重要概念,也是 C 语言的一个重要特色。在 C 语言中,指针被广泛使用,它和数组、字符串、函数间数据的传递等有着密不可分的联系。可以说,没有掌握指针就没有掌握 C 语言的精华。
指针可以对内存中各种类型的数据进行快速、直接的处理,也可以为函数间的数据传递提供简洁、便利的方法。正确熟练地使用指针有利于编写高效的程序,但指针使用不当也容易导致严重错误。指针很灵活,也很危险。
指针变量(简称指针)就是存放地址的变量。其声明形式与一般变量声明相比只是在变量名前多一个星号*,接下来看两个例子。
例1:
int *p;
该例中声明了变量 p 为指向整型值的指针(即变量 p 中可以存放某个整型变量的地址)。这里的*在声明语句中,是指针说明符,表示声明的变量是指针变量。
例2:
float *xPtr, *yPtr, f;
该例子中声明了两个指向浮点型值的指针 xPtr 和 yPtr 以及一个浮点型变量 f 。
① 指针可以赋值为 NULL 或某个地址。具有值 NULL 的指针不指向任何地址;
② 指针是具有特定属性的地址。光有地址只是知道数据存储在内存的某个位置,但怎么访问该位置的数据(即访问多少位,以什么方式访问)还需要指针类型来明确;
例如:使用 int 类型的指针访问其所指向的数据时,会一次性读取32位( int 类型的数据是32位),并使用整型数据的格式访问该数据。所以指针不仅仅是一个地址,还必须有特定的属性(类型)。
③ 数组名可以看成是一个特殊的地址,首先数组名是地址(数组的首地址),其次数组名有属性(数组元素的类型),所以可以把数组名赋值给同类型的指针变量。
例如:
char s[10] = "China";
char *sptr = s;
第二条语句赋值后 sptr 拥有和 s 同样的值,即数组的首地址,也就是存储字符‘C’的单元的地址。
④ 要访问指针所指向的单元可以使用间接引用运算符*(不同于前面声明语句中的*,这里的*在表达式中,是运算符),*也被称为复引用运算符,它返回其操作数(指针)所指向的对象;
例如:
char s[10] = "China";
char *sptr = s;
cout << *sptr;
将输出指针 sptr 所指的单元中存储的字符(因为 sptr 是 char 类型的指针),也就是输出字符‘C’。
⑤ 可以通过指针的复引用修改指针所指向的单元;
例如:
char s[10] = "China";
char *sptr = s;
*sptr = 'c';
上述代码会将该存储单元中大写字符的‘C’修改为小写字符的‘c’。
请注意前面代码的输出语句和下面的代码语句的区别:
char s[10] = "China";
char *sptr = s;
cout << sptr;
这条语句将输出字符串“China”。之前学习字符数组时应该知道,语句cout << s;会输出数组 s 中存储的整个字符串,实际上 C++ 在使用 cout 输出 char 类型指针时,不是输出字符指针的值(地址),而是输出从该地址开始的字符串(逐个输出一个个字符,直到碰到 '\0' 为止)。所以cout << sptr;和cout << s;的作用一样,都是输出字符串“China”。
⑥ 访问一个字符串一般也是使用该字符串的首字符的地址;
⑦ 指针也可以参与算术运算。指针加上或减去一个整数 n,其运算结果是指向指针当前指向变量的后方或前方的第 n 个变量。
例如:
之前 sptr 指向字符‘C’的存储单元,执行语句sptr++;后 sptr 则指向字符‘h’的存储单元。如下语句:
while(*sptr != '\0') sptr++;
则可以使指针 sptr 指向该字符串后面的 '\0'。
如果要输出字符串中的部分内容,也可以通过修改指针实现,如:
char s[10] = "China"; char *sptr = s; sptr++; cout << sptr;
上述代码执行语句sptr++;后,指针 sptr 指向了字符 'h' 的存储单元,此时cout << sptr;输出的是 sptr 指向的字符串,即“hina”。
⑧ 同类型的两个指针可以参与各种关系运算,其结果可以反映两指针所指向的地址之间的位置前后关系。
例如:
int a[10]; int *p = a, *q = &a[1]; if(p > q) cout << "p>q" << endl; else cout << "p<=q" << endl;
上述代码中指针 p 中存放的是 a 的值,也就是 a[0] 的地址,q 中存放的是 a[1] 的地址,而数组元素是按序连续存储的,所以 q 的值要比 p 的值大,程序输出p<=q。
三、用指针传递参数
C 和 C++ 函数调用的参数传递方式有两种:传值和传引用。
如果采用传值的方式传递指针值,可以实现类似于传引用的效果。
例如:
#include
using namespace std;
// 函数inc:将p指向的整数值加
// 参数:p-int类型指针,指向要加的整数
void inc(int * p)
{
(*p)++; // *p 访问 p 指向的单元,++ 将该单元的数据加
// 注意不能是 *p++, 因为 * 和 ++ 优先级相同,且右结合,这种写法修改的是 p 的值,而不是 *p 的值
}
int main()
{
int a = 10;
inc(&a); // 调用 inc 函数,修改 a 的值(传递的是 a 的地址)
cout << a << endl; // 输出 a 的值
return 0;
}
上述程序的输出为11,其中被调用函数 inc 只修改了 main 函数中的局部变量 a 的值,但并没有修改实参的值(实参是&a,即 a 的地址依然没变)。
C 和 C++ 提供了一系列操作字符串的函数,要使用这些函数只要在代码的头文件部分包含 string.h 即可。
常用的字符串处理函数见下表:
函数原型 |
函数功能 |
char * strcpy(char *dest,const char *src) |
将字符串 src 复制到 dest |
char * strcat(char *dest,const char *src) |
将字符串 src 添加到 dest 末尾 |
char * strchr(const char *s,int c) |
检索并返回字符 c 在字符串 s 中第一次出现的位置 |
int strcmp(const char *s1,const char *s2) |
比较字符串 s1 与 s2 的大小,若 s1 串大于 s2 串则返回一个大于 0 的值;若 s1 串等于 s2 串则返回值为 0;若 s1 串小于 s2 串则返回一个小于 0 的值。 |
size_t strlen(const char *s) |
返回字符串 s 的长度 |
char * strncat(char *dest,const char *src,size_t n) |
将字符串 src 中最多 n 个字符复制到字符串 dest 中 |
int strncmp(const char *s1,const char *s2,size_t n) |
比较字符串 s1 与 s2 中前 n 个字符 |
char * strncpy(char *dest,const char *src,zise_t n) |
复制 src 中的前 n 个字符到 dest 中 |
char * strstr(const char *s1,const char *s2) |
扫描字符串 s1,并返回第一次出现 s2 的位置 |
char * strtok(char *s1,const char *s2) |
检索字符串 s1,该字符串 s1 是由字符串 s2 中定义的定界符所分隔 |
在一个长串中查找子串可以使用strstr函数,该函数的函数原型为:
char* strstr(const char* s1, const char* s2);
该函数从 s1 所指字符串中第一个字符起,顺序向后查找出与 s2 所指字符串相同的子串,若查找成功则返回该子串首次出现的首地址,否则返回 NULL。
例如:
char *a="abcdeabcde";
char *b="bcd";
cout<
该程序输出结果为“bcdeabcde”,因为strstr(a, b)的返回值为“bcd”在“abcdeabcde”中第一次出现的首地址,所以用 cout 输出时,从该位置的字符开始,逐个输出直到 '\0',即字符串“bcdeabcde”。
当然,查找子串时,也可以从长串的某个位置开始。
例如:
char *a="abcdeabcde";
char *b="bcd";
cout<
该程序的输出为“bcde”。因为a+4得到一个新地址,即 a 指向的字符串中第一个字符‘e’的地址,从该位置开始查找 b 指向的字符串‘bcd’,得到从字符‘e’开始的第一个“bcd”出现的地址,然后用 cout 输出该地址开始的字符串,即“bcde”。
值得注意的是,在实际运用该函数过程中,总是出现错误,几经debug,最终明确了其const char* 的重要意义:在调用该函数时(尤其实在循环中调用),未结束对该函数的调用之前,不能改变其参数的值!包括下面的strlen函数在内,存在const限定的函数调用,都应该避免此情形。
另外,下次从什么地方开始查找子串?应该是上次找到子串的开始位置加上子串的长度。其中,函数strlen可以计算字符串的长度,其函数原型为:
int strlen(const char *s);
函数 strlen 只有一个参数 s,它是一个字符指针,代表了一个字符串,函数计算 s 指向字符串的长度并返回。
例如:
char *a="x";
char *b="Hello world!";
cout<
用于截取子字符串。
形式 : s.substr(pos, len)
返回值: string,包含s中从pos开始的len个字符的拷贝(pos的默认值是0,len的默认值是s.size() - pos,即不加参数会默认拷贝整个s)
异常 :若pos的值超过了string的大小,则substr函数会抛出一个out_of_range异常;若pos+n的值超过了string的大小,则substr会调整n的值,只拷贝到string的末尾
总结
指针用的好可以让程序清晰高效,用的差晦涩冗杂,正如开篇语所言:指针很危险,也很灵活。而没有掌握指针,也就没有掌握 C 语言的精华。
substr函数部分借鉴于CSDN博主「哦啦哦啦!」的原创文章,于此处声明。