指针
指针
指针变量的声明和初始化
间接引用
指针变量存储的是内存的地址,间接引用一个值。
指针的声明
指针的声明如下:
type *countptr,count //type指的是类型,说明countptr是一个指向类型为type的指针,而count是一个类型为type的变量 |
注意每一个指针变量名前面都必须有前缀的星号,建议每个声明只声明一个变量。
指针的初始化
应该初始化为nullptr,或者为一个相应类型的地址。nullptr指的是空指针。
指针运算符
地址运算符&
&是一个一元运算符,它获得操作数的内存地址。
int y=5; |
这样,yPtr将获得y的地址,yPtr间接引用变量y的值。
注意这里与引用变量声明中的&用法不同,后者的前面总是有一个type,在声明一个引用时,&是变量的一部分。
间接运算符*
又称为间接引用运算符,返回的当前指针指向的变量。借助上例
cout<<*yPtr<<endl; |
两者的显示结果都是5。
也可以在赋值时使用:
*yPtr=9; |
这将把输入的值存放到y中。
注意不要间接引用一个未初始化的指针,否则可能会产生严重后果(不小心修改重要的数据),也不要间接引用一个空指针。
使用指针的按引用传递方式
指针也可以像引用传递一样,将指向大型数据的对象的指针传递给函数,减少值传递对象所需要的开销。
注意:传递的是指向这个变量的指针(以值传递的方式,被复制到了函数对应的指针形参中),然后被调用的函数就可以通过间接引用这个指针来访问这个变量,从而完成了按引用传递。
内置数组
声明内置数组
type name [size] |
访问内置数组的元素
//使用[]来访问内置数组中的单个元素 |
初始化内置数组
//使用初始化列表中的值初始化这些元素,若提供的初始化值数目少于元素的个数,则剩下的元素是有值的初始化 |
将内置数组传递给函数
注意:内置数组的名字可以隐式地转换为这个内置数组第一个元素的内存地址。
若数组名为arrayName,则它可以隐式地转换为&arrayName[0]。
所以将其传递到函数时,只需要将简单地传递其数组名即可,这样,函数就可以修改数组中的任意一个元素,除非在数组名前面加上const,表明这些元素不可以被修改。(这样可以防止在函数体中修改原始的内置数组)
声明内置数组形参
格式如下:
int f(const int values[]) |
这将表明这个数组不应该被这个函数修改。
还可以写成:
int f(const int *values) |
注意:编译器是无法区分接受一个指针的函数和接受一个内置数组的函数,因为当编译器遇到const int values[]的一维内置数组的函数形参时,它将转换为指针表示形式的const int *values。
这两种形式是可以互换的,但建议:当实参是一个内置数组时,还是使用[]。
C++11:标准库函数begin和end
sort(a.begin(),a.end()) |
这表明a的整个数组都应该被排序(vector类或者array类)。
sort(begin(n),end(n)); |
也可以对内置数组进行排序,注意n是内置数组名。
内置数组的局限性
1、无法使用关系运算符和相等运算符进行比较
2、不能相互赋值
3、不知道自己的大小
4、不提供自动边界检查的功能,可能会越界。
所以推荐使用array和vector。
使用const修饰指针
考虑一个情况,打印一个数组,所以其中的元素和数组的大小不应该改变。
注意内置数组很容易按引用的方式被传递,很容易在被调函数中被修改,所以需要const关键字修饰。
建议:
1、如果一个值在它被传递到的函数中没有(或不应该)改变,那么这个参数应该被声明为从const。
2、在使用一个函数前,确定哪些值需要被修改,哪些值则不需要。
将指针传递给函数的方式有4种:
1、指向非const数据的非const指针
2、指向const数据的非const指针
3、指向非const数据的const指针
4、指向const数据的const指针
指向非const数据的非const指针
访问权限最高,可以通过指针间接修改数据,也可以修改指针,使其指向其他数据。
声明一般为
type *ptr; |
指向const数据的非const指针
可以被修改以指向任何适当类型的其他数据项,但是不能通过该指针来修改它所指向的数据
声明如下:
const int *ptr |
其实很好理解,const修饰的是int,所以它不能修改它所指向的数据。
当一个函数被调用时,其实参为内置数组,则实参的内容将被有效地按引用传递给函数。当传递的是指向对象的指针时,只会复制对象的地址,然而对象本身并没有被复制。
建议:
1、如果大型对象不需要在被调用函数中修改,则使用指向const数据的指针或者const数据的引用来传递它们,避免按值传递的复制开销
2、使用指向const数据的指针或者const数据的引用来传递大型对象,可以获得按值传递的安全性
3、应该采用按值传递的方式向函数传递基本类型的实参(如int、double等),除非调用者明确要求被调函数可以直接修改调用者中的值
指向非const数据的const指针
它始终指向同一个内存位置,通过该指针可以修改这个位置上的数据。
注意:声明为const的指针必须在它们被声明时被初始化,但如果这样的指针是函数的形参,那么就可以用传递给函数的指针来初始化它
声明如下:
int const *ptr |
const修饰的是*
指向const数据的const指针
它具有最小的访问权限:总是指向内存中的相同位置,并且不能用指针修改这个内存位置的数据。
建议:如果一个内置数组作为实参,并且该实参只是作为可读,那么就应该使用该类型的指针。
sizeof运算符
可以用于在编译期间确定内置数组或者其他任意数据类型、变量或者常量的字节大小。
当用于一个内置数组时,将返回该内置数组的字节大小,返回值是size_t类型。
注意:在一个函数中,使用sizeof关键字来获得一个内置数组形参的字节数,结果会得到一个指针的字节数,而不是该内置数组的字节数
可以使用:
sizeof numbers/sizeof(numbers[0]) |
来获得一个内置数组的元素个数。
确定基本类型、内置数组和指针字节大小
使用sizeof运算符,可以计算用于大部分标准数据类型的字节数。
sizeof运算符可以用于任何表达式或者任何类型名。
当sizeof用于一个变量名时(不是一个内置数组时)或其他表达式时,返回的用于存储该表达式的特定类型的字节数。
//只有在类型名作为sizeof的操作数时,才需要使用圆括号 |
注意,sizeof是一个编译时的运算符,不会对操作数进行求值,其所得的值是一个size_t类型的常量表达式。
建议:在不同的系统中,用来存储一个特定数据类型的字节数可能不同,如果编写的程序依赖于数据类型的字节大小,则需要总是使用sizeof来确定存储数据类型所需要的字节数。
指针表达式和指针算术运算
指针算术运算只适用于指向内置数组元素的指针。
一个指针可以++、--、+=(+)一个整数、-=(-)一个整数、或者一个指针可以减去另外一个同类型指针(注意只适用于指向同一内置数组元素的两个指针)。
注意:指针算术运算的结果取决于指针所指向的对象的大小,所以指针算术运算是与机器有关的。
以下两种写法是等效的:
int *vPtr=v; |
指针加上与减去整数
当指针加上或者减去一个整数时,它是加上或者减去这个整数与该指针指向对象的字节大小的乘积,字节数取决于对象的数据类型。
如下所示:
int *vPtr=&v[0]; |
此时vPtr指针指向的是v[2]。
假设vPtr原本指向的v[0]的内存地址为3000,因为一个int数据占据4字节内存,所以vPtr现在指向的地址为3008,也就是v[2]的地址。
如果vPtr增加到了3016,则指向的是v[4]。
也可以进行回退,只需要vPtr-=4即可,此时指针指向的是v[0],也就是这个数组的开始地址。
vPtr-=4; |
如果指针+1或者-1,则可以使用自增或者自减运算符。++指向下一个元素,--指向前一个元素。
注意:指针的算术运算符是没有边界检查功能的,所以必须确保在指针指向的元素在内置数组的范围内。
指针相减
指向同一个内置数组的指针变量可以相减,如:
vPtr包含地址3000(v[0]),vPtr2包含地址3008(v[2]),则:
x=vPtr2-vPtr; |
恰好对应的就是这段区间内有几个元素(注意是v[0]、v[1])。
指针算术运算只有在指向内置数组的指针上进行时才有意义,否则这是一个逻辑错误。
指针赋值
两个指针为同一类型时,则可以将一个指针赋值给另外一个指针,否则必须使用强制类型转换(通常是reinterpret_cast),这将把赋值运算符右侧的指针值转换为左侧的指针类型。
但是存在例外,即void指针(void*),这是一种通用指针,可以表示任何类型的指针。
任何指向基本类型或者类类型的指针都可以被赋值给void*类型的指针,而不需要强制类型转换。
但是,void类型的指针是不能直接赋值给其他类型的指针的,必须先把void 类型的指针强制转换为合适的指针类型。
不能间接引用void*指针
因为void指针只是一个包含未知数据类型的内存地址,编译器不知道该指针所指向的确切字节数和数据类型(编译器必须知道这两个才能正常编译,否则无法确定特定指针的数据类型,对于void指针来说,无法确定字节数),所以会报错。
注意:将一个类型的指针赋值给另外一个类型的指针时,需要将前者强制类型转换。
对于void*指针的合法操如下:
1、将void*指针与其他指针进行比较。
2、将void指针强制类型转换为其他类型的指针和将地址赋值给void 指针。
除上述外,其他均是非法操作。
指针比较
可以使用相等和关系运算符进行比较。
只有在指针指向同一数组的元素时,使用关系运算符才是有意义的。
指针比较的是存储在内存中的地址。在一个数组中,指向内置数组中下表编号大一些的元素的指针比指向内置数组中下表编号小一些的元素的指针大。
一个常用的指针比较是判定其是否为0、nullptr或者NULL。
指针和内置数组的关系
用以下的声明进行介绍:
int b[5]; |
指针/偏移量表示法
b[i]可等价表示为
*(b+i); |
这里的i指的是偏移量,联系前面的指针加法、减法运算可以很好理解,注意*优先级大于+,所以需要加上圆括号。
当然,&b[i]也可以写成
b+i; |
以内置数组名作为指针的指针/偏移量表示法
其实本质跟上面是一样的,这里我们就直接用数组名即可,因为数组名就是执行数组首元素的指针。
*(b+i) |
等价于b[i]。
指针/下标表示法
和内置数组一样,指针也可以带下标,如
bPtr[1] |
这和b[1]是等价的。
内置数组名不可以修改
b+=3; |
上述是一个非法操作,试图用算术运算来修该内置数组名的值。
这也说明,数组名是一个指向非const数据的const指针。
建议:使用内置数组表示法,不要使用指针表示法。
基于指针的字符串
字符与字符常量
字符是C++源程序的基本构建块。
一个字符常量是一个整数,表示为用一对单引号引起来的一个字符。
字符串
一个字符串可以看作一个被视为整体的字符序列,包含字母、数字、特殊字符。
基于指针的字符串
是一个以'\0'结尾的内置字符数组,'\0'标记了字符串在内存中结束的位置。
可以通过指向字符串第一个字符的指针来访问该字符串,对其进行sizeof运算得到的是包括空字符在内的这个字符串的长度。
数组名也是指向该内置数组第一个元素的指针。
字符串文字作为初始化值
以下两者等效:
char color[]="blue"; |
注意两者都包含以'\0'作为结尾。
注意字符串文字是static存储类别的,在程序执行时一致存在,可以被共享,也可以不被共享。
建议:如果要修改字符串文字的内容,先将它存储到一个内置数组中。
字符常量作为初始化值
char color[]={'b','l','u','e','\0'}; |
这是初始化列表的形式,编译器会根据初始化列表中的初始化值的个数,来决定上述内置数组的大小。
建议:
1、需要分配好足够的空间,来存储结束字符串的空字符。
2、要时刻记得包含结束的空字符。
访问C字符串的字符
可以使用内置数组的下标表示法直接访问字符串中单个的字符。
使用cin读取字符串到char类型的内置数据中
可以使用cin通过流提取读取一个字符串到一个内置字符数组中,例子如下:
cin>>word |
这将读取一个字符串到名为word的内置字符数组中,该数组有20个元素。
用户输入的字符串存储到word中,直到遇到空白字符或者文件结束符为止,注意,该字符数组的长度不能超过19个字符,以便为结束空字符流出空间。
可以使用setw流操纵符来保证不超过读入word的字符串不会超过内置字符数组的长度
cin>>setw(20)>>word; |
这样将指定最多只有19个字符到word中,留下一个用于存储结束空字符。
注意setw只作用于下一个要输入的值,如果输入多了,那么剩下的字符不会被存储到word中。
使用cin.getline()读取文本行到char类型的内置数据中
getline()函数有三个参数:一个存储该行文本的内置字符数组,一个长度,一个定界字符。
char sentence[80]; |
这将读取一行文本到内置数组中。
当遇到定界字符'',或者输入了文件结束符,或者已经读入的字符数比长度参数小1时,函数将停止读取。
第三个参数的默认值时'',并且内置数组的最后一个字符是留给结束空字符的,如果遇到定界字符,则会读取并丢弃它(这个时候将会结束继续输入字符到内置数组中,不管是否继续输入字符串)。
char a[50]; |
显示字符串
cout<<sentence; |
cout不关心字符数组的大小,直到遇到终止符为止,空字符不会输出。