终于来到第三天,今天我们来聊聊其他类型的指针,我尝试使用章节来叙述,增强可读性.
目录
const 型指针
1、const 型指针
聊到指针,我们下意识的认为指针所指向的数据是“变量”,这是因为我们常常会混淆“指针变量”和“指针所指的数据”这两个概念。指针大多数时刻指向的是变量,但有时指针也会指向常量,定义指向常量的指针需要在定义语句的指针类型前加上 const,表示指向的对象是常量。
const int a=78;
const int b=28;
下面有两种常见错误,分别对应着一种概念难点:
const int a=78;
const int b=28;
int c=23;//定义一个常量
const int*p1=&a;//一个指向常量 a 的常量指针
*pi=58;//error:不可以修改常量指针指向的常量![1]
pi=&b;//correct:常量指针可以修改[2]
[1]:为什么不可以修改常量指针指向的常量?常量,字如其名,在本次运算里是不变的值,这就意味着无法通过间接引用来修改该值
[2]:为什么常数指针却又可以修改,指向不同的常量呢?那是因为指针本身是变量,允许修改变量的值(也有例外)
const 指针也被称为“只读”指针,因为定义指向常量的常量指针只限定指针的间接访问(引用)只能读而不能写,却没有对指针值的读写进行限制。
举一个例子,在 C 的学习里我们遇到过将一个数组值赋予另一个数组的任务,显然不能通过一个=来解决,在 c 中我们常用 for 循环来解决,这里有一个更加简洁的方法:
//test pointer const mode 2021-7-20-16:33
#include <iostream>
using namespace std;
void mystrcpy(char*atg, const char*temp)
{
while(*atg++=*temp++);//temp 指针得到了多次调用!因为其值可写
}
int main()
{
char a[20]="You look beautiful!";
char b[20];
mystrcpy(b,a);
cout<<b<<endl;
return 0;
}
result:
"/Users/yuwenao/C++ Learning/cmake-build-debug/untitled5"
You look beautiful!
进程已结束,退出代码为 0
2、指针常量
顾名思义啦,指针本身就是常量!
在指针定义语句的指针名前加 const,表示指针本身是常量。
char*const pc="good"; //定义方法
pc="a girl"; //error:指针常量!无法改变指针值
*pc="b"; //correct:可以改变指针所指量之值
*(pc+1)="c"; //correct
*pc++="d"; //error:常量无法自增自减!!
const int*b=28;
int*const pi=&b; //error:无法将(int*)型指针转换为(int*const)型!
上面是一些使用警示!其实这两种指针都可以从字面上理解他们的本质,理解了本质,属性和性质自然不难。
定义指针常量必须初始化,否则无法确定地址
3、指向常量的指针常量
是不是很绕?一步一步理解:“指向常量”如字面意思,所指的数据是常量,这意味着这个指针对这个数据只有“读”的属性,不能“写”;“指针常量“,如上文所述,指针是常量,所指的地址是一定且确定的。两者结合,证明这个数据类型十分多限制,而由于它有指针常量的属性,定义指针常量必须初始化,否则无法确定地址。
const int ci=7;//定义一个常量
int ai;
const int* const cpc=&ci;//指向常量的常量指针
const int* const cp1=&ai;
cpi=&ci;//error:指针值无法修改
*cpi=39;//error:所指对象无法修改
ai=39;//correct!!!
指针与函数
1、传递数组的指针性质
一旦将数组作为参数传递到函数,则在栈上定义了指针,且可以对该指针进行加减操作。数组名和指针是等价的。
通过指针,我们可以实现一个函数”返回“多个值。这里牵涉到传值和传址的问题。例如:
//locationan & swap 2021-7-20 20-20-
#include<iostream>
using namespace std;
void swap(int,int);
int main() {
int a = 3, b = 8;
cout<<"a="<<a<<",b=" << b << endl;
swap(a, b);
cout << "after swapping...\n";
cout <<"a=" <<a << ",b=" << b << endl;
}
void swap(int x,int y) {//交换两个形参
int temp = x;
x= y;
y = temp;
}
要实现的是两个数据的交换,我们看看结果:
"/Users/yuwenao/C++ Learning/cmake-build-debug/untitled5"
a=3,b=8
after swapping...
a=3,b=8
进程已结束,退出代码为 0
整了张内存图
可以看到,两个数据没有成功交换。因为函数参数值的传递是实参到形参的复制,被调函数内部对形参的修改并不反映到调用函数的实参中,致使swap()中x和y作了交换,而main()中a和b并没有交换。传递指针可以使函数“返回”更多的值。这里的“返回”不是函数返回类型描述返回值的返回,而是反映了调用函数中的变量给被调函数修改了。例如,上面程序的完成版:
//locationan & swap 2021-7-20 20-31-correct
#include<iostream>
using namespace std;
void swap(int*,int*);
int main() {
int a = 3, b = 8;
cout<<"a="<<a<<",b=" << b << endl;
int*p1=&a,*p2=&b;
swap(p1, p2);
cout << "after swapping...\n";
cout <<"a=" <<a<< ",b=" <<b<< endl;
}
void swap(int*x,int*y) {//交换两个形参
int temp = *x;
*x= *y;
*y = temp;
}
结果是:
"/Users/yuwenao/C++ Learning/cmake-build-debug/untitled5"
a=3,b=8
after swapping...
a=8,b=3
进程已结束,退出代码为 0
成功了,这就是传址,只有传址可以把函数值返回到原来变量处(可能从栈到全局变量区、堆区等)当然,处理好的数据(传址)是先从部分函数的栈出,到主函数的栈处储存,等待下一步操作。
我们再一次见识到了指针的强大——对硬件的绝对调控。但是仔细读读我们的标题:强大而危险的灵魂,你是不是在想,如此强大的伟力会带来怎么样的隐患?
没有错,指针的灵活是以破坏函数的黑盒特性为代价的,好了,有点兄弟会问,什么是黑盒啊?所谓“黑盒”,是指从用户的角度来看一个程序时,并不关心其内部构造和实现原理,而只关心程序的功能及如何使用这个程序,运用这些功能。它使函数可以访问本函数的栈空间以外的内存区域(函数的副作用初露端倪),以致引起了以下问题:
(1)可读性差:因为间接访问比直接访问相对难理解,函数声明与定义也相对比较复杂。
(2)重用性低:函数调用依赖于调用函数或整个外部内存空间的环境,丧失了黑盒的特性,所以无法作为公共的函数模块来使用。
(3)调试复杂:跟踪错误的区域从函数的局部栈空间扩大到整个内存空间。不但要跟踪变量,还要跟踪地址。错误现象从简单的不能得到相应的返回结果,扩展到系统环境遭破坏甚至死机。
3、指针函数
返回指针的函数。注意:指针函数不能将其内部说明的具有局部作用域的数据地址作为返回值,为什么?例如:
// pointer function 2021-7-20-21-51
#include<iostream>
using namespace std;
int*getInt(char*str)//指针函数
{
int value=20;
cout<<str<<endl;
return &value; //warning
}
void smoefn(char*str)
{
int a=40;
cout<<str<<endl;
}
int main()
{
int*pr=getInt("input a value:");
cout<<*pr<<endl;
smoefn("It is uncertain.");
cout<<*pr<<endl;
return 0;
}
该程序 getInt()函数返回一个局部作用域变量地址是不妥当的,因为该函数结束时,其栈内的变量 value 随之消失!在编译时,编译器给出了 warning。
很多同学问:诶,为什么在使用了 smoefn()函数后,指针间接引用的值改变了,明明主函数没有相关对该指针所指的变量重新赋值的操作啊?这里同学忽略了一个点,那就是两个函数的参数——指针是一样的,还原一下工作过程:在 main 中,指针 pr 得到了一个局部变量的地址且输出该地址之内容为“20”,随后调用了 smoefn()函数,该函数将前一个函数之栈空间作为自己工作的栈空间,因此函数返回后,是返回到这个曾经存储了“20”的栈空间里。
注意:可以返回堆地址、全局数据、静态变量数据地址,切勿返回局部变量地址!
4、void 指针
void 指针不指向任何类型,仅仅是一个地址(或者说储存一个地址),不能进行指针运算、间接引用。但空指针在两个指针交换数据时尤为关键,类似于 temp,但记得赋值给空指针时要强制类型转换!!!
字符指针
1.字符串的表示
在C++中,字符串是用双引号括起来的字符序列,简称字串,又称字符串字面值。当字符串用于字符数组初始化时,其在完成将内容填写到所创建的字符数组中之后,随即消失,不再另辟存储空间;而当字符串用于表达式,或输出,或赋值,或作参数传递,则其在运行中有它自己的存储空间,可以寻址访问。例如:
char buffer[] = "hello";//字符数组初始化
cout <<"good" <<endl;//字符串用于输出
在上面,"hello"用来给字符数组初始化,"good"字符串用来输出。
字符串的类型是指向字符的指针(字符指针char*),它与字符数组名同属于一种类型。字符串在内存中以'\0'结尾。这种类型的字符串称为C字符串,或ASCIIZ字符串(ASCII序列后跟Zero之意)。
2、字符指针
字符串、字符数组名、字符指针都是同一种数据类型。
输出字符指针就是输出字符串,
输出字符指针的间接引用就是输出单个字符!
关于字符串的比较,我想先聊聊字符串的属性:我找了一张描述内存的绝世好图
字符串存放在 data 区的 const 小区里,若为全局变量,则存放于 globel 区(全局区)或者 Common 区(静态区),当编译器遇到一串字符串时,就将之存放于 const区且以‘\0’结尾,这样字符串就变成了“地址”,因为她有地址这一属性,所以同样的两个字符串“加拿大电鳗”在内存中地址不一,因此两个字符串比较其实就是地址比较,同理,也是数组名地址的比较,那有同学想,诶,我要的不是这种奇奇怪怪的比较啊,我是想逐个字符的比较啊,我该怎么做?
字符串比较应该是逐个字符一一比较,通常使用标准库函数 strcmp(),它在 string.h或cstring头文件中声明,其原型为:
int strcmp(const char* strl,const char* str2);
其返回值如下:
(1)当strl串等于str2串时,返回值0;
(2)当strl串大于str2串时,返回一个正值;
(3)当strl串小于str2串时,返回一个负值。
3、字符串赋值
C++中可以使用字符串对一个字符数组初始化,但是却不能对一个已存在,初始化成功的字符数组赋值,因为数组名是常量,无法充当左值。
可以用 strcpy()对字符数组赋值,它包含在 string.h 中。原型为:
char *strcpy(char *dest, const char *src) //把 src 所指向的字符串复制到 dest。
需要注意的是如果目标数组 dest 不够大,而源字符串的长度又太长,可能会造成缓冲溢出的情况。如何应用?情况实例:
#include <stdio.h>
#include <string.h>
int main ()
{
char str1[]="Sample string";
char str2[40];
char str3[40];
strcpy (str2,str1);
strcpy (str3,"copy successful");
printf ("str1: %s\nstr2: %s\nstr3: %s\n",str1,str2,str3);
return 0;
}
result:
str1: Sample string
str2: Sample string
str3: copy successful
完成于 2021-7-20-22:45 致理楼 L1