C++ Primer学习系列(2):数组和指针/表达式/语句

4 章 数组和指针

4.1

数组的类型:可以是内置类型或类类型;除引用之外,数组元素的类型还可以是任意的复合类型。没有所有元素都是引用的数组。

数组的维数:只能是编译期即能确定值的常量表达式,包括整型常量、枚举常量或者用常量表达式初始化的整型 const对象。

字符串常量:为了与 C兼容, C++中的字符串常量包含一个额外的空字符用于结束字符串。因此用字符串来初始化数组时要注意数组长度至少要比串中字符个数多 1

注意:不允许数组直接复制和赋值

下标类型:用下标访问 string时,下标为 string::size_type类型;访问 vector时,下标为 vector::size_type类型;访问 bitset,数组时,下标为 size_t类型(通常为 unsigned int)

4.2

指针:可初始化为常量 0 <cstdlib>中的预处理器变量 NULL

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

void*指针:可以保存任何类型对象的地址。它表明保存的是一个地址,但不清楚此地址上的对象的类型,因此不允许使用 void*指针操纵它所指向的对象。

void*指针支持的操作:与另一个指针进行比较;向函数传递 void*指针或从函数返回 void*指针;给另一个 void*指针赋值

指针相减操作:返回值为 ptrdiff_t标准库类型,在 <cstddef>中定义,为 signed的。各平台上通常实现为 signed int

C++允许计算数组或对象的超出末端的后一个地址,但不允许对此地址进行解引用操作,也不允许再继续往后计算地址。计算数组首地址之前的地址是不合法的。

指向 const对象的指针: const double *cptr;表示不能通过 cptr修改其指向的对象的值。注意 cptr本身不是 const,因此定义时可以不初始化, cptr本身值可以改变(指向另一个对象); cptr可以指向 const对象,也可以指向非 const对象。记住 cptr一经定义,就不允许修改其所指对象的值,如果它指向非 const对象,同样要遵守这个规则。指向 const的指针常用作函数的形参。

不能使用指向 const对象的指针修改基础对象,然而如果该指针指向的是一个非 const对象,可用其他方法修改其所指的对象

const double pi=3.14;

double *ptr=&pi; //error:指向非 const的指针不能指向 const对象

const double *cptr=&pi //ok:指向 const对象

double dval=3.14;

const double *p=&dval; //ok:指向非 const对象

不能使用 void*指针保存 const对象的地址,而必须使用 const void*类型的指针保存 const对象的地址

const指针:指针本身的值不能修改,因此定义时必须初始化

int errNumb=0;

int *const curErr=&errNumb;

指向 const对象的 const指针:

const double pi=3.14159;

const double *const piptr=&pi;

指针和 typedef:

string s;

typedef string *pstring;

const pstring cstr=&s; // const指针,从右往左读,写成下面形式更易理解

pstring const cstr=&s; //三种形式等价

string *const cstr=&s;

这里不能当作是文本扩展 const string *cstr;认为 cstr是指向 const对象的指针。实际上, pstring是指针类型, const修饰的是指针类型,因此 cstr const指针,等价于 string *const cstr;一般编程习惯易写成第一种形式,其实从右往左读,写成第二种形式更易理解

小结:“指向 const对象的指针”可以指向 const对象,也可以指向非 const对象;“指向非 const对象的指针”只能指向非 const对象;“ const指针“表示指针本身的值不能变

4.3

C风格字符串:以空字符 null(/0)结束的字符数组。 C++中的字符串常量实际上就是 const char类型的数组,隐式地以空字符结束

C++通过 const char*类型的指针来操纵 C风格字符串

C风格字符串的标准库函数:在 <cstring>中定义。有 strlen(s),strcmp(s1,s2),strcat(s1,s2),strcpy(s1,s2),strncat(s1,s2,n),strncpy(s1,s2,n),传递给这些库函数的指针必须具有非零值,并且指向以 null结束的字符数组中的第一个元素。

注意:永远不要忘记字符串结束符 null;调用者必须确保目标字符串具有足够的大小;尽可能使用标准库类型 string

创建动态数组:

string *psa=new string[10]; //类类型数组,调用默认构造函数来初始化各元素值

int *pia=new int[10];//内置类型数组,若为局部作用域,则不进行初始化

int *pia2=new int[10]();//进行值初始化,这里各元素值初始化为 0

const对象的动态数组:

const int *pci_bad=new const int[100];//error:元素是 const对象,定义时必须初始化

const int *pci_ok=new const int[100]();//ok:值初始化,这也是唯一的初始化方式

const string *pcs=new const string[100];//ok:类类型必须有默认构造函数

允许动态分配空数组:

char arr[0]; //error

char *cp=new char[0]; //ok:cp不能进行解引用操作,因为它没有指向任何元素

动态空间的释放:动态数组的释放用 delete [] arrname;这里 arrname必须是指向数组首个元素的指针。注意如果遗漏了空方括号对,这是一编译器无法发现的错误,将导致程序在运行时出错(至少会导致运行时少释放内存空间,从而产生内存泄漏)

混合使用标准库类 string C风格字符串: C风格字符串与字符串常量具有相同数据类型,都是以 null结束。因此可用 C风格字符串初始化 string对象,但不能直接用 string对象初始化字符指针,而是要先调用 string类的 c_str()成员函数转化成 C风格字符串

string类的 c_str():返回指向字符数组首地址的指针,其指向的是 const char类型的数组,以结束符 null结束

char *str=str2; //compile-time type error

char *str=st2.c_str(); //almost ok,but not quite

const char *str=st2.c_str(); //ok

4.4

指针和多维数组:

int ia[3][4]; //ia指向了 ia[0], ia &ia[0]

int *ip[4]; //指针数组: ip是一个数组,数组的每个元素是 int*类型的指针

int (*ip)[4];//数组指针: ip是一个指针,指向有 4个元素的数组(指向其首个元素)

ip=&ia[2];

// typedef简化

typedef int int_array[4]; //int_array是有 4个元素的数组类型

int_array *ip=ia;

总结:

数组、指针、 void*指针、指向 const对象的指针、 const指针、 C风格字符串、动态创建数组、多组数组

 

5 章 表达式

5.3

左移操作符 <<在右边插入 0以补充空位;右移操作符 >>,若是无符号数,则左边开始插入 0,若是有符号数,则插入符号位的副本或 0值(取决于具体实现)。移位操作的右操作数不可以是负数,且必须是严格小于左操作数位数,否则操作的效果未定义

bitset对象的使用:可以使用 set(pos),reset(pos)等操作,也可用下标操作符来引用 , b[pos]

I/O库重载了移位操作符 >> <<:重载的操作符与该操作符的内置类型版本有相同的优先级和结合性

移位操作符具有中等优先级:比算术操作符低,比关系操作符、赋值操作符和条件操作符优先级高

IO操作符为左结合:返回左值

5.4

赋值操作符:左操作数必须是非 const的左值。赋值操作符 =为右结合,返回左值

复合赋值操作符 : 10个, +=,-=,*=,/=,%=,<<=,>>=,&=,^=,|=

注意: a op= b;等价于 a=a op b;但使用复合赋值操作时,左操作数只计算了一次,而使用长表达式时,该操作数计算了两次(降低了性能)

最佳实践:养成使用复合赋值操作的习惯

5.5

自增和自减操作符:有前置和后置两种形式,前置式 ++i 1后返回对象 i本身,后置式 i++先保存 i的值并返回(为右值),然后加 1,可见后置式做的工作更多,降低了性能

最佳实践:养成使用前置操作的习惯

组合使用解引用和自增操作:

vector<int>::iterator iter=ivec.begin();

while(iter!=ivec.end())

cout<<*iter++<<endl; //是后置式的自增操作,等效于 *(iter++),先对 iter解引用并返回

//结果,然后 iter 1,注意 ++优先级高于解引用 *

5.6

箭头操作符 ->:访问指针所指对象的成员,若用点操作符来访问,则要先对指针解引用

Sales_item *sp=&item1;

(*sp).same_isbn(item2);

sp->same_isbn(item2);

5.7

条件操作符: cond ? expr1:expr2

5.8

sizeof操作符:返回 一个对象或类型名的长度,返回值类型为 size_t,单位为字节。 Sizeof表达式的结果是编译时常量

sizeof(type name);

sizeof(expr);

sizeof expr;

5.11

new delete表达式: new无法获取需要的内存时,抛出 bad_alloc异常

如果指针指向不是用 new分配的内存地址,则在该指针上使用 delete是不合法的

注意:若指针的值为 0,则在其上做 delete操作是合法的

const对象的动态分配和回收:由于是 const对象,故指针也必须是指向 const对象的指针

const int *pci=new const int(1024); //直接初始化

const int *pci2=new const int; //没有初始化

const string *pcs=new const string; //默认初始化

const string *pcs2=new const string(); //值初始化

int *pi=new int(); //值初始化

delete pci;

5.12

有符号的整数与无符号整数一起运算时,有符号整数会转化成无符号整数

类型转换:指向任意数据类型的指针都可以转换成 void*类型,整型数值常量 0可转换成任意指针类型

枚举类型: C++将枚举类型的对象或枚举成员转换为整型

转换为 const对象:

int i;

const int ci=0;

const int &j=i; //const对象初始化 const引用,系统将非 const对象转换为 const对象

const int *p=&i;

由标准库类型定义的转换 :

string s;

while(cin>>s)

会检验 istream流的状态,并转换成 bool类型的相应值

显示转换:也称为强制类型转换 (cast),命名的强制转换操作符有 static_cast,dynamic_cast,const_cast reinterpret_cast

dynamic_cast:支持运行时识别指针或引用所指向的对象

const_cast:去掉表达式的 const性质

const char *pcstr;

char pc=string_copy(const_cast<char*>(pcstr)); // 接受 char* 类型实参

static_cast:编译器隐式执行的转换都可由 static_cast显示完成

double dval;

int ival;

ival*=dval; //为避免先转换成 double,再把结果转换成 int,可如下写

ival*=static_cast<int>(dval); //直接把 dval转换成 int

reinterpret_cast:为操作数的位模式提供较低层次的重新解释,即将操作数内容解释为另一种不同的类型

int *ip;

char *pc=reinterpret_cast<char* (ip); //以后编译器就认为 pc指向 char对象,但运行时

//pc实际指向的是 int对象

string str(pc); //可能引起运行时的怪异行为

旧式的强制转换:用圆括号将类型括起来实现。

ival=ival*(int)dval;

这是为了向后兼容,标准 C++中引入命名的强制转换操作符,更清晰明确

注意:强制类型转换本质上是非常危险的(如导致精度出现较大损失)。强制类型转换关闭或挂起了编译器正常的类型检查,因此尽量少使用。

总结:

算术操作符、关系操作符和逻辑操作符、位操作符、赋值操作符、自增和自减操作符、箭头操作符、条件操作符、 sizeof操作符、逗号操作符、操作符优先级和结合性、 new delete表达式、类型转换

 

6 章 语句

6.4

语句的控制结构中定义的变量必须初始化,且作用域限制在语句体内

6.6

switch-case语句: case标号的值都必须是一个常量表达式; switch后的表达式内可以定义和初始化一个变量

对于 switch结构,只能在它的最后一个 case标号或 default标号后面定义变量

6.7

在循环条件中定义的变量在每次循环里都要经历创建和撤销的过程

6.9

do-while语句:在循环条件中引用的变量必须在 do语句之前就已经存在

6.12

goto语句: goto语句和获得转移控制权的带标号语句必须位于同一函数内

6.13

throw表达式:抛出异常,以分号结束

if(!item1.same_isbn(item2))

throw runtime_error(“Data must refer to same ISBN”);

std::cout<<item1+item2<<std::endl;

runtime_error是标准库异常类中的一种,在 stdexcept头文件中定义

每一个标准库异常类都定义了 what()成员函数,返回 const char*类型的 C风格字符串

嵌套 try块寻找 catch处理代码:寻找顺序与函数调用链刚好相反,被调用函数内没找到时就到调用函数中找,直到顶层函数。若不存在 catch子句,程序运行会跳转到 terminate标准库函数, terminate函数在 <exception>中定义,通常让程序非正常退出

标准异常:

<exception>:中定义了异常类 exception,只通知异常的产生,不会提供更多信息

<stdexcept>:定义了几种常见的异常类, exception,runtime_error,range_error,overflow_error,underflow_error,logic_error,domain_error,invalid_argument,length_error,out_of_range

<new>:定义了 bad_alloc异常类,提供因无法分配内存而由 new抛出的异常

<type_info>:定义了 bad_cast异常类

注意:标准库异常类提供的操作只有创建、复制异常类型对象以及对象赋值。 exception,bad_alloc bad_cast只定义了默认构造函数,其他异常类只定义了一个使用 string初始化的构造函数,因此初始时必须提供 string参数

6.14

大多数编译器都会内置的预处理变量:

NDEBUG:调试标志,用于有条件地调试代码

int main()

{

#ifndef NDEBUG

cerr<<”starting main”<<endl;

#endif

//...

}

默认情况下, NDEBUG未定义,因此会执行 #ifndef/#endif之间的代码,当编译提供对应的 DNDEBUG命令行选项时,则表示定义了 NDEBUG变量,如 g++ -DNDEBUG main.cpp,相当于在文件开头提供 #define NDEBUG命令。

__FILE__:文件名

__LINE__:当前行号

__TIME__:文件被编译的时间

__DATE__:文件被编译的日期

assert预处理宏:在 <cassert>中定义, assert(expr),只要 NDEBUG未定义, assert宏就求解表达式 expr,为 false时, assert输出信息并终止程序,否则 assert什么也不做

总结:

表达式语句、声明语句、复合语句、 if语句、 switch语句、 while循环语句、 for循环语句、 do- while循环语句、 break语句、 continue语句、 goto语句、 return语句、 try-catch语句、 throw表达式、编译器内置预处理变量、 assert预处理宏

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值