C++ Primer 第4章数组和指针
引言:
现在C++程序应当尽量使用vector和迭代器类型,而避免使用低级的数组和指针。
4.1 数组
4.1.1 数组的定义和初始化
数组的维数必须用大于等于1的常量表达式定义:
1)整型字面常量
2)枚举常量
3)常量表达式初始化的整型const对象。
(非const变量以及要到运行阶段才知道其值的const变量都不能用于定义数组的维数)
例如:
int staff_size=27;
const unsigned sz=get_size(); //const value not known until run time
double salaries[staff_size]; //error:non const variable
double test_scores[get_size()]; //error:non const expression
int valus[sz]; //error:size not known until run time
1.显示初始化数组元素
(1)const unsigned array_size=3;
int ia[array_size]={1,2,3};
(2)int ia[]={0,1,2};
当初始化提供的元素少于维数时,补0.
当初始化提供的元素多于维数是,则后面的元素自动丢掉。
2. 特殊的字符数组
char ca1[]={‘c’,’+’,’+’}; 3维
char ca2[]={‘c’,’+’,’+’,’\0’}; 4维
char ca3[]=”C++”; 4维
3.不允许数组直接复制和赋值
4.1.2 数组操作
数组元素可用下标操作符访问,数组下标的正确类型是size_t。
int main()
{
const size_t array_size=10;
int ia[array_size];
for(size_t ix=0;ix!=array_size;++ix)
ia[ix]=ix;
}
4.2 指针的引入
与迭代器一样,指针提供对其所指对象的间接访问,与迭代器不同的是,指针用于指向单个对象,而迭代器只能用于访问容器内的元素。
4.2.2 指针的定义和初始化
每个指针都有一个与之关联的数据类型,该数据类型决定了指针所指向的对象的类型。
1.指针变量的定义
vector<int> *pvec; //pved can point to a vector<int>
int *ip1,ip2; //ip1 and ip2 can point to an int
string *pstring;
double *dp;
2.另一种声明指针的风格
string* ps; //legal but can be misleading
3.连续声明多个指针的格式
string *ps1;
string *ps2;
或
string *ps1,*ps2;
4.避免使用未初始化的指针
5.指针初始化和赋值操作的约束
6.指针初始化和赋值操作的约束
(1)0值常量表达式(const的0值,以及字面常量0)
(2)类型匹配的对象的地址
(3)另一对象之后的下一地址(string *p=”Hello”; string *q=++p;)
(4)同类型的另一有效指针
int val;
int *p1=val; //error can’t convert int to ‘int *’
int *p2=&val; //ok
int zero=0;
int *p3=zero; //error
int *p4=0; //ok,指针为空
const int a=0;
int *p5=a; //ok
#include<cstdlib>
int *p=NULL; //ok
注意1:引入NULL(头文件 cstdlib),其值为0.如果代码中使用了这个预处理变量,则编译时会自动被数值0替换。因此把指针初始化为NULL等效于初始化为0值。
注意2:初始化或赋值时必须保证类型匹配。
7 void*指针
他可以保存任何类型对象的地址:
void*表明该指针与一地址值相关,但是因为不知道类型,所以不知道其长度,所以不能有void*指针对所指对象进行操作。
它能做的操作:
(1) 与另一指针进行比较
(2) 向函数传递void*指针或从函数返回void*指针
(3) 给另一void*指针赋值
4.2.3 指针操作
对指针的解引用可访问它所指的对象。
sttring s(“hello world”);
string *sp=&s;
cout<<*sp; //prints hello world
1.生成左值的解引用操作
对左操作数进行解引用,则修改的是指针所指对象的值。
如果没有使用解引用,则修改的是指针本身的值。
string s1(“some value”);
string *sp1=&s1; //sp1指向 s1,其中s1中存的值为”some value”
*sp1=”a new value” //sp1指向s1,其中s1中存的值为”a new value”
string s2(“another”)
string *sp2=&s2;
sp1=sp2; //sp1指向s2,其中s2中存的值为”another”
2.指针和引用的比较
引用和指针之间的区别:
(1) 引用总是指向某个对象,定义引用时没有初始化是错误的。
(2) 赋值行为的差异:给引用赋值修改的是该引用所关联的对象的值,而并不改变引用与另一个对象的关联。引用一经初始化,就始终指向同一个特定对象。
例如:
int ival=1024,ival2=2048;
int *pi=&iavl,*pi2=ival2;
pi=pi2; //pi now points to ival2
赋值后,ival对保持不变,赋值操作修改了pi指针的值,是指针指向了另一个不同的对象。
int &ri=ival, &ri2=ival2;
ri=ri2;
这个赋值操作修改了ri引的值ival对象,而并非引用本身。
3.指向指针的指针
int ival=1024;
int *pi=&ival;
int **ppi=*pi;
输出ival的值:
cout<<ival<<*pi<<**ppi<<endl;
4.2.4 使用指针访问数组元素
int ia[]={0,2,4,6,8};
int *ip=ia; //ip points to ia[0]
ip=&ia[4]; //对数组中的其他元素可以用下标的地址进行访问
1.指针的算术操作
ip=ia; //ok:ip points to ia[0]
int *ip2=ip+4; //ok:ip2 points to ia[4]
注意:指针的算数操作只有在元指针和计算出来的新指针指向统一数组的元素,或指向该数组粗糙年空间的下一单元时才合法。
指针之间的减法操作:
ptrdiff_t n=ip2-ip; //ok:distance between the points
两个指针减法操作的结果是 标准库类型ptrdiff_t的数据。
在cstddef头文件中定义。size_t是unsigned类型(必须为正),而ptrdiff_t则是signed类型(求距离,可正可负)。
2.解引用和指针算术操作符之间的相互作用
指针上加一个整型数值,其结果仍然是指针。允许在这个结果上直接进行解引用操作,而不必把它先赋给一个新指针:
int last=*(ia+4); //ok
3.下标和指针
int ia[]={0,2,4,6,9};
int i=ia[0];
int *p=&ia[2];
int j=p[1]; // ia[3]
int k=p[-2]; //ia[0]
4.计算数组的超出末端指针
const size_t arr_size=5;
int arr[arr_size]={1,2,3,4,5};
int *p=arr;
int *q=p+arr_size;
C++允许计算数组或对象的超出末端的地址,但是不允许对次地址进行解引用操作。
5.输出数组元素
只要定义的多个变量具有相同的类型,就可以在for循环的初始化语句中同时定义它们。
const size_t arr_sz=5;
int int_arr[arr_sz]={0,1,2,3,4};
for(int *pbegin=int_arr,*pend=int_arr+arr_size;pbegin!=pend;++pbegin)
cout<<*pbegin<<’’;
6.指针是数组的迭代器
4.2.5 指针和const限定符
1.指向const对象的指针
(1)如果指针指向const对象,则不允许用指针来改变其所指的const值,为了保证这一特性,C++强制要求指向const对象的指针必须具有const特性:
const double *cptr; //cptr may point to a double that is const
(2)把一个const对象的地址赋给一个普通的、非const对象的指针也会导致编译时错误:
*cptr=42; //error :*cptr may be const
const double pi=3.14;
double *ptr=π // error
const double *cptr=π //ok
(3)不能使用void*指针保存const对象的地址,而必须使用const void*
const int universe=4.2;
const void *cpv=&universe; //ok
void *pv=&universe; //error
(4) 允许把为const对象的地址赋给指向const对象的指针:
double dval=3.4;
cptr=&dval;
在实际程序中,指向const的指针常用作函数的形参。将形参定义为指向const的指针,以此确保传递给函数的实际对象在函数中不因为形参而被修改。
2.const 指针
const指针必须在定义时初始化。已经初始化不得再修改,即使给curErr赋回同样的值也不行:
int errNumb=0;
int *const curErr=&errNumb;
curErr=curErr; //error: curErr is const
对于const指针指向的地址中存的值是否能被修改,就要看其定义的是什么类型了。如此处的是int类型,所以可以进行修改:
*curErr=0; //OK
3.指向const对象的const指针
const double pi=3.14;
const double *const pi_ptr=π
指针与指向的值均不能修改。
4.指针与typedef
typedef string *pstring;
const pstring cstr;
等价于:
string *const cstr;
注意:阅读const声明语句产生的部分问题,源于const限定符既可以放在类型前也可以放在类型后:
string const s1;
const string s2;
一下三种是一个含义:
string s;
typedef string *pstring;
const pstring cstr1=&s;
pstring const cstr2=&s;
string *const cstr3=&s;
4.3 C风格字符串
字符串字面值(const char 类型的数组)就是C风格字符串的实例。
C风格字符串就是以null结束的字符串数组:
char ca1[]={‘C’,’+’,’+’}; //not C-style
char ca2[]={‘C’,’+’,’+’,’\0’};
char ca3[]=”C++”;
const char *cp=”C++”;
char *cp1=ca1;
char *cp2=ca2;
其中 ca1和cp1都不是C风格字符串
1.C风格字符串的使用
C++语言通过(const)char*类型的指针来操控C风格字符串,直到达到结束符null为止:
const char *p=”some value”;
while(*cp)
{
++cp;
}
2.C风格字符串的标准库函数
首先必须包含C头文件:
#include<cstring>
strlen(s) |
返回s的长度,不包括字符串结束符null |
strcmp(s1,s2) |
若s1和s2相同,返回0,s1>s2返回正数,s1<s2返回负数 |
strcat(s1,s2) |
将字符串s2连接到s1后,并返回s1 |
strcpy(s1,s2) |
将s2复制给s1,并返回s1 |
strncat(s1,s2,n) |
将s2的前n个字符连接到s1后面,并返回s1 |
strncpy(s1,s2,n) |
将s2的前n个字符复制给s1,并返回s1 |
3.永远不要忘记字符串结束符null
char ca[]={‘C’,’+’,’+’};
cout<<<strlen(ca)<<endl;
此例题中,ca是一个没有null结束符的字符数组,则计算结果不可预知。strlen会读到一直遇到null为止。
4.调用者必须确保目标字符串有足够的大小
标准库函数strcat和strcpy的第一个实参数组必须具有足够大的空间存放新生成的字符串。不然会造成溢出的错误。
5.使用strn函数处理C风格字符串
6.尽可能使用标准库类型string
4.3.1 创建动态数组
普通数组 |
动态数组 |
长度固定,在编译是必须知道长度 |
可以在运行时动态地分配数组 |
只在定义它的语句内存在 |
一直存在,直到程序显示释放它为止 |
每个程序在执行时都占有一块可用的内存空间,用于存放动态分配的对象,此空间称为程序的 自由存储区 或 堆。C语言中用标准库函数malloc和free来在自由存储区内分配存储空间。而C++则用new和delete表达式实现相同的功能。
1.动态数组的定义
动态分配数组时,只需要指定类型和数组长度,不必为数组对象命名,new表达式返回新分配数组的第一个元素的指针:
int *pia=new int[10];
在自由存储区中创建的数组对象是没有名字的,程序员只能通过其地址间接地访问堆中的对象。
2.初始化动态分配的数组
动态分配数组时:
(1)如果数组元素具有类类型,则自动调用默认构造函数。
(2)如果数组元素是内置类型,则无初始化
string *psa=new string[10]; //均初始化为空字符串
int *pia=new int[10]; //没有初始化
(3)也可以用跟在数组长度后面的一对空圆括号,对数组进行初始化
int *pia2=new int[10]();
注意:对于动态分配的数组,其元素只能初始化为元素类型的默认值,而不能像数组变量一样,用初始化列表为数组元素提供各不同的初始值。
3.const对象的动态数组
const int *ip=new const int[100]();
这样的函数用处不大。
4.允许动态分配空数组
也就是,可以等需要的维数的值确定以后,在建立数组。
size_t n=get_size(); //等带执行后返回
int *p=new int[n];
for(int* q=p;q!=p+n;++q)
.......
注意:如果get_size返回0则,程序任然可以正确执行。
如果n是0,则仍然可以成功调用new,但p并没有指向任何对象。
5.动态空间的释放
delete [] pia; // []不要掉,掉了的话,编译器也无法检测
6.动态数组的使用
动态数组的使用就是因为在编译时无法确定长度。
4.3.2 新旧代码的兼容
1.混合使用标准库类string和C风格字符串
C风格字符串都是以空字符null结束的。
(1)可以使用C风格字符串对string对象进行初始化或赋值。
(2)string类型的加法操作需要两个操作数,可以使用C风格字符串作为其中的一个操作数,也允许将C风格字符串用作复合赋值操作的右操作数。
反正,要求C风格字符串的地方不可以直接使用标准库string类型对象。要调用成员函数c_str。其返回类型是 const char类型的数组。
const char *str=st2.c_str();
2.使用数组初始化vector对象
使用数组初始化vector对象,必须指出用于初始化的第一个元素以及数组最后一个元素的下一位置的地址:
const size_t arr_size=6;
int int_arr[arr_size]={0,1,2,3,4,5};
vector<int> ivec(int_arr,int_arr+arr_size);
4.4 多维数组
1.多维数组的初始化
int ia[3][4]={
{0,1,2,3},
{4,5,6,7},
{8,9,10,11}
};
//explicitly initialized only element 0 in each row
int ia[3][4]={{0},{4},{8}};
2.多维数组的下标引用
对多维数组进行索引,每一维都需要一个下标。
3.指针好多维数组
使用多维数组名,实际上就是将其自动转换为指向数组第一个元素的指针。
int ia[3][4];
int (*ip)[4]=ia; //ip points to an array of 4 ints
注意:圆括号必不可少,可以理解为ip是指向含有4个元素的数组的指针
ip=&ia[2]; //ia[2]is an array of 4 ints
4.用typedef简化指向多维数组的指针
typedef int int_array[4];
int_array *ip=ia;
for(int_array *p=ia,p!=ia+3;++;)
for(int *q=*p;q!=*p+4;++q)
cout<<*q<<endl;
转载于:https://www.cnblogs.com/GaoYuan-whut/archive/2013/01/24/2875035.html
C++ Primer 第4章数组和指针相关推荐
- C Primer Plus (第五版)中文版——第 10 章 数组和指针
10.1 数组 数组(array)由一系列类型相同的元素构成.数组声明(array declaration)中包括数组元素的数目和元素的类型.如: int month[12]; /* 12个整数的数 ...
- C Primer Plus 第10章 数组和指针 10.5 指针操作
2019独角兽企业重金招聘Python工程师标准>>> C提供了6种基本的指针操作,下面的程序将具体演示这些操作.为了显示每一个操作的结果,程序将打印出指针的值(即指针指向的地址). ...
- 第五章 数组和指针的关系
如果对声明数组的语句不太明白的话,请参阅我前段时间贴出的文章<<如何 理解c和c++的复杂类型声明>>. 数组的数组名其实可以看作一个指针.看下例: 例八: ...
- 第 10 章 数组和指针
1 /*--------------------------------- 2 array2d.c -- 处理二维数组的函数 3 ---------------------------------*/ ...
- 第6章 数组、指针与字符串(一)基于范围的for循环
转载于:https://www.cnblogs.com/ZHONGZHENHUA/p/10231299.html
- 第6章 数组、指针与字符串(二)指针与函数
转载于:https://www.cnblogs.com/ZHONGZHENHUA/p/10235024.html
- 第一章:数组与指针概念剖析
第一章 数组与指针概念剖析 收藏 数组与指针生来就是双胞胎,多数人就是从数组的学习开始指针的旅程的.在学习的过程中,很自然就会经常听到或见到关于数组与指针的各种各样的看法,下面我节选一些在各种论坛和文 ...
- 第二章 数组名是一个指针常量吗?
数组名是一个指针常量这种观点来源于数组名在表达式计算中与指针的结果等效性.例如下面的代码: int a[10], *p = a, *q; q = a + 1; q = p + 1; 在效果上看,a + ...
- 第五章 指向数组的指针
讲到第五章了,数组两个字还离不开我们的左右,数组的内容也真多,另一方面也因为数组与指针的关系的确非常密切. 通常,对于int a[8][9]这个二维数组,我们可以这样定义一个指向它的指针: int ( ...
最新文章
- Fastcgi是什么
- HDU 4556 Stern-Brocot Tree
- AD9910高速集成DDS芯片(芯片阅读笔记-串行模式篇-22个寄存器SPI通信周期控制)
- Linux环境编程 哈希链表结构 hlist 介绍与用例
- (一) shario教程资料
- poi 默认2位小数_odoo小数精确度
- HDU5126 stars(4维偏序->cdq套cdq+树状数组)
- 【P1835】小红花
- workList in DCMTK问题总结
- LeetCode(867)——转置矩阵(JavaScript)
- jupyter ipython display_ipython jupyter notebook中显示图像和数学公式实例
- matlab各种文件类型,MATLAB中的文件类型总结
- jenkins(六):Jenkins节点管理
- DirectX11环境配置
- arcgis js for JavaScript 4.X 移动轨迹动画
- SQL学习笔记(01)_LIKE、IN、通配符
- 动态图相册 android,动态图相册app|动态图相册下载_v1.36_9ht苹果下载
- 小米笔记本 wifi linux,被小米笔记本的WiFi坑了一道
- 使用uniapp编写词霸每日一句页面
- 古筝d调变降e调怎么办_古筝转调方法_古筝怎么转调
热门文章
- 决策树随笔-深度AI科普团队
- 实战深度强化学习DQN-理论和实践
- java集合合并_【Java必修课】各种集合类的合并(数组、List、Set、Map)
- html5标签属性大全_HTML/HTML5 知识点思维导图
- Linux下Elasticsearch-2.4.0的安装与简单配置(单节点)Head插件安装(已测试)
- java自动获取ip_java自动获取电脑ip和MAC地址
- 电信设置的nat 虚拟服务器192.168.1.3 是什么,VMware WorkStation的三种网络连接方式详解...
- Java基础---数组练习(最大值、最小值的索引)
- 【2015蓝桥杯省赛】C++ B组试题
- Canny边缘检测及C++实现