指针的引入

vector的遍历可以使用下标或迭代器实现,同理,也可用下标或指针(pointer)来遍历数组.指针是指向某总类型对象的复合数据类型,是用于数

组的迭代器:指向数组中的一个元素.在指向数组元素的指针上使用解引用操作符*(dereference operator)和自增操作符++(increment

operator),与在迭代器上的用法类似.对指针进行解引用操作,可获得该指针所指对象的值.而当指针做自增操作时,则移动指针使其指向数组中

的下一个元素.在使用指针编写程序之前,我们需进一步了解一下指针.

4.2.1 什么是指针

对初学者来说,指针通常比较难理解.而由指针错误引起的调试问题连富有经验的程序员都感到头疼.然而,指针是大多数C程序的重要部分,而且

在许多C++程序中仍然受到重用.

指针的概念很简单:指针用于指向对象.与迭代器一样,指针提供对其所指对象的间接访问,只是指针结构更通用一些.与迭代器不同的是,指针用

于指向单个对象,而迭代器只能用于访问容器内的元素.

具体来说,指针保存的是另一个对象的地址:

string s("hello world");
string *sp=&s;  //sp holds the address of s
第二条语句定义了一个指向string类型的指针sp,并初始化sp使其指向string类型的对象s.*sp中的*操作符表明sp是一个指针变量,&s中的&符

号是取地址(address-of)操作符,当此操作符用于一个对象上时,返回的是该对象的存储地址.取地址操作符只能用于左值,因为只有当变量用作

左值时,才能取其地址.同样地,由于用于vector类型、string类型或内置数组的下标操作和解引用操作生成左值,因此可对这两种操作的结果做

取地址操作,这样即可获取某一特定对象的存储地址.

------------------------------------------------------华丽的分割线----------------------------------------------------------

建议:尽量避免使用指针和数组

指针和数组容易产生不可预料的错误.其中一部分是概念上的问题:指针用于低级操作,容易产生与繁琐细节相关的(bookkeeping)错误.其他错

误则源于使用指针的语法规则,特别是声明指针的语法.

许多有用的程序都可不使用数组或指针实现,现代C++程序采用vector类型和迭代器取代一般的数组、采用string类型取代C风格字符串.

-----------------------------------------------------华丽,不要出界----------------------------------------------------------

4.2.2   指针的定义和初始化

每个指针都有一个与之关联的数据类型,该数据类型决定了指针所指向的对象的类型.例如,一个int型指针只能指向int型对象.

1.指针变量的定义

C++语言使用*符号把一个标识符声明为指针:

vector<int> *pvec;   //pvec can point to a vector<int>
int    *ip1,*ip2;    //ip1 and ip2 can point to an int
string  *pstring;    //pstring can point to a string
double   *dp;        //dp can point to a double

-----------------------------------------------------------华丽的分割线----------------------------------------------

提示:理解指针声明语句时,请从右向左阅读.

-----------------------------------------------------------华丽-----------------------------------------------------------

从右向左阅读pstring变量的定义,可以看到

string *pstring;

语句把pstring定义为一个指向string类型对象的指针变量.类似地,语句

int *ip1,*ip2;  //ip1 and ip2 can point to an int

把ip1和ip2都定义为指向int型对象的指针.

在声明语句中,符号*可用在指定类型的对象列表的任何位置:

double dp,*dp2;  //dp2 is a pointer,dp is an object:both type double

该语句定义了一个double类型的dp对象以及一个指向double类型对象的指针dp2.

2.另一种声明指针的风格

在定义指针变量时,可用空格将符号*与其后的标识符分隔开来.下面的写法是合法的:

string* ps;  //legal but can be misleading

也就是说,该语句把ps定义为一个指向string类型对象的指针.

这种指针声明风格极容易引起这样的误解:把string*理解为一种数据类型,认为在同一声明语句中定义的其他变量也是指向string类型对象的

指针.然而,语句

string* ps1,ps2;  //ps1 is a pointer to string,ps2 is a string

实际上只把ps1定义为指针,而ps2并非指针,只是一个普通的string对象而已.如果需要在一个声明语句中定义两个指针,必须在每个变量标识符

前再加符号*声明:

string* ps1,*ps2;  //both ps1 and ps2 are pointers to string

3.连续声明多个指针易导致混淆

连续声明同一类型的多个指针有两种通用的声明风格.其中一种风格是一个声明语句只声明一个变量,此时,符号*紧挨着类型名放置,强调这个

声明语句定义的是一个指针:

string* ps1;
string* ps2;

另一种风格则允许在一条声明语句中声明多个指针,声明时把符号*靠近标识符放置.这种风格强调对象是一个指针:

string  *ps1,  *ps2;

----------------------------------------------------------华丽的分割线---------------------------------------------------

提示:关于指针的声明,不能说哪种声明风格是唯一正确的方式,重要的是选择一种风格并持续使用

--------------------------------------------------------华丽,继续----------------------------------------------------------

我个人比较喜欢第二种,看起来清楚一点,将符号*紧贴着指针变量名放置.

4.指针可能的取值

一个有效的指针必然是以下三种状态之一:保存一个特定对象的地址;指向某个对象后面的另一个对象;或者是0值.若指针保存0值,表明它不指

向任何对象.未初始化的指针是无效的,直到给该指针赋值后,才可使用它.下列定义和赋值都是合法的:

int ival=1024;
int *pi=0;  // pi initialized to address no object
int *pi2=&ival;  //pi2 initialized to address of ival
int *pi3;     //ok,but dangerous,pi3 is uninitialized
pi=pi2;       //pi and pi2 address the same object,e.g. ival
pi2=0;        //pi2 now addresses no object

5.避免使用未初始化的指针.

很多运行时错误都源于使用了未初始化的指针.

就像使用其他没有初始化的变量一样,使用未初始化的指针时的行为C++标准中并没有定义使用未初始化的指针(爆寒,这句话翻译的我看不清楚

- -),它几乎总会导致运行时崩溃.然而,导致崩溃的这一原因很难发现.

对大多数的编译器来说,如果使用未初始化的指针,会将指针中存放的不确定值视为地址,然后操纵该内存地址中存放的位内容.使用未初始化的

指针相当于操纵这个不确定地址中存储的基础数据.因此,在对未初始化的指针进行解引用时,通常会导致程序崩溃.

C++语言无法检测指针是否未被初始化,也无法区分有效地址和由指针分配到的存储空间中存放的二进制位形成的地址.建议程序员在使用之前

初始化所有的变量,尤其是指针.

-------------------------------------------------------华丽的分割线又来了------------------------------------------------

实践告诉你:如果可能的话,除非所指向的对象已经存在,否则不要先定义指针,这样可避免定义一个未初始化的指针.

如果必须分开定义指针和其所指向的对象,则将指针初始化为0.因为编译器可检测出0值的指针,程序可判断该指针并未指向一个对象.

-------------------------------------------------------华丽的分割线,好饿----------------------------------------------------

6.指针初始化和赋值操作的约束

对指针进行初始化或赋值只能使用以下四种类型的值:

(1).0值常量表达式,例如,在编译时可获得0值的整型const对象或字面值常量0

(2).类型匹配的对象的地址.

(3)另一对象末的下一地址.

(4)同类型的另一个有效指针.

把int型变量赋给指针是非法的,尽管此int型变量的值可能为0.但允许把数值0火灾编译时可获得0值的const量赋给指针:

int ival;
int zero=0;
const int c_ival=0;
int *pi=ival;  //error: pi initialized from int value of ival
pi=zero;       //error:pi assigned int value of zero
pi=c_ival;     //ok: c_ival is a const with compile-time value of 0
pi=0;          //ok: directly initialize to literal constant 0

可以看出,除了直接使用0或在编译时值为0的const量外,还可以使用C++语言从C语言中继承下来的预处理器变量NULL,该变量在cstdlib头文件

中定义,其值为0.如果在代码中使用了这个预处理器变量,则编译时会自动被数值0替换.因此,把指针初始化为NULL等效于初始化为0值:

//cstdlib #defines NULL to 0
int *pi=NULL;  //ok:equivalent to int *pi=0;

正如其他的预处理器变量一样,不可以使用NULL这个标识符给自定义的变量命名.

-----------------------------------------------------------华丽的分割线-------------------------------------------------

注解:预处理器变量不是在std命名空间中定义的,因此其名字应为NULL,而非std::NULL.

---------------------------------------------------------饿的华丽无比-------------------------------------------------

除了两种例外情况之外,指针只能初始化或赋值为同类型的变量地址或另一指针:

double dval;
double *pd=&dval;   //ok: initializer is address of a double
double *pd2=pd;     //ok:initializer is a pointer to double
int *pi=pd;   //error: types of pi and pd differ
pi=&dval;     //error:attempt to assign address of a double to int *

由于指针的类型用于确定指针所指对象的类型,因此初始化或赋值时必须保证类型匹配.指针用于间接访问对象,并基于指针的类型提供可执行

的操作,例如,int型指针只能把其指向的对象当作int型数据来处理,如果该指针确实指向了其他类型(如double类型)的对象,则在指针上执行的

任何操作都有可能出错

7.void*指针

C++提供了一种特殊的指针类型void*,它可以保存任何类型对象的地址:

double obj=3.14;
double *pd=&obj;
//ok: void* can hold the address value of any data pointer type
void *pv=&obj;   //obj can be an object of any type
pv=pd;          //pd can be a pointer to any type

void*表明该指针与一地址值相关,但不清楚存储在此地址上的对象的类型.

void*指针只支持几种有限的操作:

a.与另一个指针进行比较;
b.向函数传递void*指针或从函数返回void*指针;
c.给另一个void*指针赋值.

不允许使用void*指针操作它所指向的对象.

习题4.10   下面提供了两种指针声明的形式,解释宁愿使用第一种形式的原因:

int *ip;
int* ip;

第一种形式强调了ip是一个指针,在阅读的时候不容易被误解,尤其是当一个语句中同时定义多个指针的时候.

习题4.11   解释下列声明语句,并指出哪些是非法的,为什么?

a. int *ip;
b. string s,*sp=0;
c. int i;double* dp=&i;
d. int* ip,ip2;
e. const int i=o,*p=i;
f. string *p=NULL;

a合法.b合法.c非法,i跟dp两者类型不一致.d中定义了一个ip指针和一个整型变量ip2.e合法,定义了一个const int型对象和一个指向const

int型对象的指针p,i初始化为0,p也初始化为0.f合法,定义指向string对象的指针p,并将它初始化为0.

习题4.12   已知一指针p,你可以确定该指针是否指向一个有效的对象吗?如果可以,如何确定?如果不可以,说明原因.

无法确定某指针是否指向一个有效对象,因此在C++语言中,无法检测指针是否未被初始化,也无法区分一个地址是有效地址,还是由指针所分配

的存储空间中存放的不确定值的二进制位形成的地址

习题4.13    下列代码中,为什么第一个指针的初始化是合法的,而第二个则不合法?

int i=42;
void *p=&i;
long *lp=&i;

具有void*类型的指针可以保存任何类型对象的地址,因此p的初始化是合法的;而指向long型对象的指针不能用int型对象的地址来初始化,因此

lp的初始化不合法.
 

<think>我们有一个C语言程序,它从标准输入读取一行字符串(最多1000个字符,因为数组大小为1001,包括换行符和结尾的空字符),然后判断这个字符串是否是回文(palindrome)。程序在读取字符串后,设置一个标志flag,初始为0。然后遍历字符串的前一半,如果发现有任何对称位置不相等,就将flag设为1并跳出循环。最后根据flag的值输出"yes"或"no"。 用户要求:引入指针来实现相同功能。 原代码步骤: 1. 定义一个字符数组a[1001]。 2. 使用getchar()循环读取字符,直到遇到换行符或者达到数组上限1000(因为要留一个位置给空字符)。 3. 在字符串末尾添加空字符'\0'。 4. 使用for循环比较对称位置的字符,判断是否为回文。 引入指针的修改思路: 我们可以用指针遍历数组,而不是使用数组下标。具体来说,我们可以用两个指针,一个指向字符串的开头,另一个指向字符串的结尾,然后同时向中间移动并比较字符。 修改步骤: 1. 读取字符串的部分可以保持不变,因为我们仍然需要一个数组来存储字符串。 2. 在判断回文的部分,我们定义两个指针:一个指向数组的起始位置(即a[0]),另一个指向字符串的最后一个有效字符(注意,我们存储的字符串以'\0'结尾,所以最后一个有效字符在a[b-1]的位置,因为我们在读取后把换行符替换成了空字符,实际上原代码中a[b]='\0',而有效字符索引从0到b-1)。 3. 然后,我们让第一个指针从头向后移动,第二个指针从尾向前移动,直到两个指针相遇(或者发现不相等)。 注意:由于字符串长度为b,我们可以设置指针: char *start = a; // 指向字符串开头 char *end = a + b - 1; // 指向字符串最后一个字符(不包括'\0') 然后循环条件为:start < end 如果*start不等于*end,则设置flag=1并跳出循环。 然后start++,end--。 这样,我们就可以用指针操作替代原来的数组下标操作。 修改后的代码: 注意:原代码中读取字符串时,当遇到换行符就停止,并且将换行符之前的一个字符设置为'\0'。实际上,原代码中a[b]='\0'是将换行符后的位置设置为结束符,而换行符本身并没有被存储(因为循环条件是在赋值后判断是否为换行符,如果是则退出循环,所以换行符没有被放入数组中)。但是请注意,在循环中,当读取到换行符时,这个换行符被赋值给了a[b],然后循环退出,紧接着a[b]被赋值为'\0',所以实际上换行符被覆盖了。 因此,我们存储的字符串是从索引0到b-1,长度为b。 现在,我们用指针重写回文判断部分: 原代码: int flag=0; for(int i=0;i<b/2;i++) { if(a[i]!=a[b-i-1]) { flag=1; break; } } 修改为: int flag = 0; char *start = a; // 指向字符串开头 char *end = a + b - 1; // 指向字符串最后一个字符(有效字符) while (start < end) { if (*start != *end) { flag = 1; break; } start++; end--; } 这样,我们就用指针实现了相同的功能。 完整代码: </think>以下是将原C语言代码中使用数组下标访问的部分,改为指针操作实现的版本。关键修改在于使用指针遍历和比较字符,而不是数组索引: ```c #include <stdio.h> int main() { char a[1001]; // 字符数组存储输入 char *p = a; // 定义指针p指向数组起始位置 int count = 0; // 字符计数器 // 指针方式读取输入 while ((*p = getchar()) != '\n' && count < 1000) { p++; // 指针后移 count++; // 计数增加 } *p = '\0'; // 字符串结束符 char *start = a; // 头部指针 char *end = a + count - 1; // 尾部指针(指向最后一个有效字符) int flag = 0; // 回文标志 // 指针比较回文 while (start < end) { if (*start != *end) { // 解引用比较实际字符 flag = 1; break; } start++; // 头指针后移 end--; // 尾指针前移 } printf(flag ? "no" : "yes"); return 0; } ``` ### 指针改造关键点说明: 1. **输入读取指针化** - 原代码:`a[b] = getchar()` + `b++` → 改为:`*p = getchar()` + `p++` - 通过指针自增操作替代下标索引 2. **回文检测指针化** - 原代码:数组下标比较 `a[i] vs a[b-i-1]` → 改为:双指针解引用比较 `*start vs *end` - 双指针分别从首尾向中间移动: - `start` 初始指向 `a[0]` - `end` 初始指向 `a[count-1]` 3. **指针运算优势** - 直接操作内存地址,减少索引计算 - 循环条件简化为地址比较 `start < end` - 移动操作更高效(`start++`/`end--`) > 此实现保持原程序功能不变:读取一行输入(最大1000字符),检测是否为回文字符串(忽略换行符),输出 yes/no 结果。[^1]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值