文章目录
0. 前言
《C++大学教程》 第8章 笔记更一下。
本章课后题要么太简单,要么太复杂,未做练习。
8. 指针
8.1 简介
指针能够实现按引用传递,并且可用于创建和操作动态的数据结构(即可以增长和缩减的数据结构),例如链表、队列、堆栈和树等。
8.2 指针变量的声明和初始化
间接引用
指针变量把内存地址作为它们的值。
通过指针引用值称为间接引用。
指针的声明
int *countPtr, count;
声明了变量 countPtr
是int *
类型的(即一个指向int
值的指针),读作“countPtr
是一个指向int的指针”。
声明中的*
只作用于countPtr
。
每一个声明为指针的变量在变量名前面必须有一个星号(*
)。
注意,声明中出现的星号(*
)不是运算符,它只是用于表明正在被声明的变量是一个指针。
指针可以被声明为指向任何数据类型的对象。
每个声明只声明一个变量,将有助于避免错误,同时可提高程序的可读性。
在指针变量名中包含字母“Ptr
”就可以清楚地表明这些变量是指针,并且应该做相应的处理。
指针的初始化
指针在声明或赋值时,应该被初始化为nullptr
(这是C++ 11的新特性),或者一个相应类型的地址。
一个值为nullptr
的指针“指向空”,被称为空指针。
对所有的指针应该进行初始化,以防止指向一个未知的或未被初始化的内存空间。
C++ 11 之前的空指针
在早期的 C++ 版本中,为空指针指定的值是0或者NULL。
把一个指针初始化为NULL和把一个指针初始化为0是等价的,但在 C++ 11 之前,按照惯例使用0。
只有这个0值是可以直接赋值给一个指针变量的整数,而无需将它先强制类型转换成一个指针类型。
8.3 指针运算符
地址运算符&
地址运算符&
是一个一元运算符,它获得操作数的内存地址。
地址运算符&
的操作数必须是一个变量名(或另一个左值),不能将地址运算符作用于常量或产生临时值(如计算结果)的表达式。
间接运算符*
一元的“*
”运算符通常称为间接运算符或间接引用运算符,它返回的是一个左值,表示其指针操作数所指向的对象。
注意,间接引用的指针也可以在赋值运算符的左侧使用,例如:
*yPtr = 9;
间接引用一个未被初始化的指针或者空指针,会导致不确定的行为。
8.4 使用指针的按引用传递方式
C++中有三种向函数传递参数的方法——
按值传递、使用引用参数的按引用传递和使用指针参数的按引用传递
使用指针参数的按引用传递的一个例子
#include <iostream>
using namespace std;
void cubeByReference( int * );
int main()
{
int number = 5;
cout << "The original value of number is " << number;
cubeByReference( &number );
cout << "\nThe new value of number is " << number << endl;
}
void cubeByReference( int *nPtr )
{
*nPtr = *nPtr * *nPtr * *nPtr;
}
cubeByReference
的函数原型在圆括号中包含int *
。
和其他类型一样,在函数原型中不需要包含指针参数的名字。
出于备档的目的,可以添加参数名,但它会被编译器忽略。
顿悟:所有参数都是按值传递的
在C++中,所有的参数都是按值传递的!
8.5 内置数组
内置数组:固定大小的数据结构
声明内置数组
为了指定一个内置数组所需的元素类型和元素个数,需要采用如下的声明形式:
类型 数组名 [数组大小]
访问内置数组的元素
内置数组的下标运算符([]
)并不提供边界检查的功能。
初始化内置数组
通过使用初始化列表可以初始化内置数组的元素。
如果提供的初始化值的数目少于元素的个数,剩下的元素是有值的初始化,也就是说基本的数值类型的元素设置为0,bool类型的设置为false,指针设置为nullptr,类的对象被它们的默认构造函数来初始化。
如果提供的初始化值多了,则产生编译错误。
如果一个内置数组的声明有初始化列表但数组的大小是省略的,那么编译器将这个内置数组的大小设置为初始化列表中的元素个数。
将内置数组传递给函数
注意,内置数组的名字的值可隐式地转换为这个内置数组第一个元素的内存地址。
因此若内置数组名是arrayName
,则它可隐式地转换为&arrayName[0]
.
在函数定义中对内置数组形参施加const
类型限定符,来防止在函数体中修改原始的内置数组。
声明内置数组形参
在函数的头部可以声明内置数组形参,形式如下:
int sumElements( const int values[], const size_t numberOfElements)
这表明这个函数的第一个参数应该是一个一维的具有int
元素的内置数组,并且该数组不应该被这个函数修改。
内置数组不知道它们自己的大小,因此处理内置数组的函数应当具有接收内置数组及其大小的相应形参。
C++ 11:标准库函数begin和end
函数sort
(和许多其他的C++标准库函数)也可以应用于内置数组。
例如,为了对内置数组n
进行排序,可以编写如下的语句:
sort( begin(n), end(n));
C++ 11 新的begin
函数和end
函数(在头文件<iterator>
中定义)每一个都接受一个内置数组实参,返回一个指针,可以用于表示在C++标准库函数如sort
中处理元素的范围。
内置数组的局限性
1. 它们无法使用关系和相等运算符进行比较,也就是说程序员必须使用一个循环来一个元素一个元素地比较两个内置数组。
2. 它们不能相互赋值。
3. 它们不知道自己的大小。
4. 它们不提供自动边界检查的功能。
在现代的C++代码中,程序员应该使用更强大array
和vector
类模板对象来表示值的列表和表格。
有些时候需要内置数组
在有些情况下必须使用内置数组。例如,处理一个程序的命令行参数。
8.6 使用const
修饰指针
为了使函数完成指定的任务,应让它有足够的权限来访问函数参数中的数据,但是权限不能过大。
在使用一个函数之前,检查它的函数原型,以确定它可以和不可以修改的参数有哪些。
将指针传递给函数有4种方式:指向非const数据的非const指针,指向const数据的非const指针,指向非const数据的const指针,以及指向const数据的const指针
8.6.2 指向const数据的非const指针
指向const数据的非const指针可以被修改以指向任何适当类型的其他数据项,但是不能通过该指针来修改它所指向的数据。
可以用这种指针为函数接收内置数组实参,函数通过它可以读取数组的元素,但不允许修改它们。
声明这样形式的指针是在指针的类型左边加一个const
,例如:
const int *countPtr
这个声明从右到左读作“countPtr
是一个指向整型常量的指针”。
如果大型对象不需要在被调用函数中修改,那么使用指向const数据的指针或者const数据的引用来传递它们,可以获得按引用传递的性能,避免了按值传递的复制开销,同时可以获得按值传递的安全性。
8.6.3 指向非const数据的const指针
指向非const数据的const指针始终指向同一内存位置,通过该指针可以修改这个位置上的数据。
声明为const的指针必须在它们被声明的时候进行初始化,但如果这样的指针是函数的形参,那么就用传递给函数的指针来初始化它。
int x;
int * const ptr = &x;
声明从右到左读作“ptr
是一个指向非const整数的const指针”。
该指针用整数变量x
的地址来初始化。
8.6.4 指向const数据的const指针
这种指针总是指向内存中相同的位置,并且不能用该指针修改这个内存位置的数据。
const int *const ptr
从右到左读作“ptr
是一个指向const整数的const指针”。
8.7 sizeof
运算符
C++ 的编译时一元运算符sizeof
,在程序编译期间确定内置数组,或者任何其他数据类型、变量或者常量的字节大小。
当sizeof
运算符应用到一个内置数组名时,它返回这个内置数组的总字节数,返回值是size_t
类型。
当sizeof
运算符作用到以内置数组作为实参的函数的指针形参,它返回这个指针的字节数,而不是该数组的大小。
使用两个sizeof
运算的结果就可以确定一个内置数组的元素个数。
例如,为了确定内置数组numbers
中的元素个数,可以使用下面的表达式(它在编译期被求值):
sizeof numbers / sizeof(numbers[0])
这个表达式用numbers
的字节数除以内置数组第0元素的字节数,得到了numbers
中的元素个数。
确定基本类型、内置数组和指针的字节大小
在不同的系统中,用来存储一个特定数据类型的字节数可能不同。如果编写的程序依赖于数据类型的字节大小,那么应该总是使用sizeof
来确定存储数据类型所需要的字节数。
sizeof
运算符可应用于任何表达式或者任何类型名。
当sizeof
应用于一个变量名(不是一个内置数组名)或其他表达式时,返回的是用于存储该表达式的特定类型的字节数。请注意,只有类型名(例如int
)作为sizeof
的操作数时,才需要使用圆括号。当sizeof
的操作数时表达式时,它不需要用圆括号。记住,sizeof
是一个编译时运算符,因此它的操作数不会被求值。
8.8 指针表达式和指针算数运算
指针算术运算只适用于指向内置数组元素的指针。
一个指针可以自增(++
)或自减(--
),可以加上(+
或+=
)一个整数,可以减去(-
或-=
)一个整数,或者一个指针减去另一个同类型的指针,后者的这一特殊的运算只适用于同一内置数组元素的两个指针。
指针加上和减去整数
当一个指针加上或减去一个整数时,它不是简单地加上或减去这个整数,而是加上或减去这个整数与该指针指向对象地字节大小的乘积。字节数取决于对象的数据类型。
指针的算术运算是没有边界检查功能的。
指针相减
指针算术运算只有在指向内置数组的指针上进行时才有意义。
将两个不指向同一内置数组元素的指针相减或者进行比较,是一个逻辑错误。
指针赋值
如果两个指针是同一类型的,那么可以把一个指针赋值给另一个指针。否则,必须用强制类型转换运算符(通常是reinterpret_cast
),将赋值运算符右侧的指针值转换为赋值运算符左侧的指针类型。
void
指针(即void *
)是一种通用指针,可以表示任何指针类型。
任何指向基本类型或类类型的指针都可以被赋值给void *
类型的指针。但是,void *
类型的指针是不可以赋值给其它类型的指针的,必须先把void *
类型的指针强制转换为合适的指针类型。
不能间接引用void *
指针
void
指针只是包含一个未知数据类型的内存地址,编译器不知道该指针所指向的确切字节数和数字类型。编译器必须知道特定指针的数据类型,才能确定该指针间接引用的字节数。对于void
指针,无法确定这样的字节数。
对void *
指针的合法运算包括:将void *
指针和其他指针进行比较,将void *
指针强制类型转换为其他指针类型和将地址赋值给void *
指针。
指针比较
指针可以使用相等和关系运算符进行比较。指针比较是比较存储在指针中的地址。
一个常用的指针比较是判定一个指针的值是否为nullptr
、0
或者NULL
(即没有任何所指的指针)。
8.9 指针和内置数组之间的关系
指针和内置数组之间关系的演示
4种引用内置数组元素的表示法:数组下标表示法、以内置数组名作为指针的指针/偏移量表示法、指针下标表示法和用指针的指针/偏移量表示法。
// Fig. 8.17: fig08_17.cpp
// Using subscripting and pointer notations with built-in arrays.
#include <iostream>
using namespace std;
int main()
{
int b[] = { 10, 20, 30, 40 }; // create 4-element built-in array b
int *bPtr = b; // set bPtr to point to built-in array b
// output built-in array b using array subscript notation
cout << "Array b displayed with:\n\nArray subscript notation\n";
for ( size_t i = 0; i < 4; ++i )
cout << "b[" << i << "] = " << b[ i ] << '\n';
// output built-in array b using array name and pointer/offset notation
cout << "\nPointer/offset notation where "
<< "the pointer is the array name\n";
for ( size_t offset1 = 0; offset1 < 4; ++offset1 )
cout << "*(b + " << offset1 << ") = " << *( b + offset1 ) << '\n';
// output built-in array b using bPtr and array subscript notation
cout << "\nPointer subscript notation\n";
for ( size_t j = 0; j < 4; ++j )
cout << "bPtr[" << j << "] = " << bPtr[ j ] << '\n';
cout << "\nPointer/offset notation\n";
// output built-in array b using bPtr and pointer/offset notation
for ( size_t offset2 = 0; offset2 < 4; ++offset2 )
cout << "*(bPtr + " << offset2 << ") = "
<< *( bPtr + offset2 ) << '\n';
} // end main
8.10 基于指针的字符串
基于指针的字符串
基于指针的字符串是一个以空字符('\0'
)结尾的内置字符数组。
对一个字符串文字进行sizeof
运算得到的是包含结束的空字符在内的这个字符串的长度。
字符串文字作为初始化值
const char * colorPtr = "blue"
创建了指针变量colorPtr
,它指向在内存某处的字符串"blue"
(以'\0'
结尾)中的字母b
。
字符串文字是static存储类别的(它们在程序执行时间内一直存在),如果程序中有多个地方引用同一个字符串文字,那么它可以被共享,也可以不被共享。
如果需要修改字符串文字的内容,那么要先将它存储在一个内置的字符数组中。
字符常量作为初始化值
char color[] = {'b', 'l', 'u', 'e', '\0'};
当声明一个内置的字符数组来包含一个字符串时,这个内置数组应该足够大,从而保证可以存储该字符串和它的结束空字符。
创建或使用一个不包含结束空字符的C风格字符串,会导致逻辑错误。
如果一个字符串长度超出要存储它的内置字符数组的长度,那么超出内置数组长度的字符将覆盖内存中内置数组后面的数据,这将导致逻辑错误和潜在的安全漏洞。
使用cin
读取字符串到char
类型的内置数据中
可以用cin
通过流提取读取一个字符串到一个内置字符数组中,直到遇到空白字符或文件结束符为止。
setw
流操作符不是黏性设置,因此只作用于下一个要输入的值。
使用cin.getline
读取文本行到char
类型的内置数据中
C++ 的cin
对象提供了函数getline
,该函数有三个参数:一个存储该行文本的内置字符数组、一个长度和一个定界字符。
例如,下面的语句
char sentence[80];
cin.getline(sentence, 80, '\n');
声明了一个具有80个字符的内置数组sentence
,并从键盘读入一行文本到这个内置数组中。当遇到定界字符'\n'
,或者输入了文件结束符,或者当已读入的字符数比第二个参数所指定的长度小于1时,函数停止读取字符。内置数组的最后一个字符是留给结束空字符的。如果遇到定界字符,则读取并丢弃它。
显示C字符串
可以用cout
和<<
输出一个内置字符数组,该数组表示了一个以空终止符结束的字符串。
和cin
一样,cout
也不关心内置字符数组的大小。字符串中的字符会被输出,直到遇到终止符为止,空字符并不会输出。
注意:cin
与cout
假定内置字符数组与以空字符结尾的字符串处理方式相同;cin
与cout
并不为其他内置数组类型提供类似的输入\输出功能。
结语
本章并不难,甚至比本科C的强度还低。
感谢念哥。
愿能静下心,改简历,搜信息~
个人水平有限,有问题欢迎各位大神批评指正!