`
xitonga
  • 浏览: 586680 次
文章分类
社区版块
存档分类
最新评论

C++ Primer 4-6

 
阅读更多
C++ Primer


第四章:数组和指针
vector和数组的区别:数组的长度是固定的,数组一经创建就不允许添加新的元素;如果数组需要更改长度,程序员只能创建一个更大的新数组,然后把原数组的所有元素复制到新数组空间中去。数组不能用另外一个数组初始化,也不能将一个数组赋值给另外一个数组。
数组定义中的类型可以是内置数据类型或者类类型;除引用之外,数组元素的类型还可以是任意的复合类型。没有所有元素都是引用的数组。


数组的维数必须用值大于等于1的常量表达式定义。此常量表达式只能包含整形字面值常量,枚举常量或者常量表达式初始化的整形const对象。非const变量以及要到运行阶段才知道其值的const变量都不能用于定义数组的维数。




char cal1[] = {'c','+','+'}; //3维
char cal2[] = {'c','+','+','/0'}; //4维
char cal3[] = {"c++"}; //4维


数组操作:
下标访问元素时,类型为size_t;


缓冲区溢出:当我们在编程时没有检查小标,并且引用了越出数组或其他类似数据结构边界的元素时,就会导致这类错误。




指针:
与迭代器不同:指针用于指向单个对象,而迭代器只能用于访问容器内的元素。




避免使用未初始化的指针:
1、除非所指向的对象已经存在,否则不要先定义指针,这样可避免定义一个未初始化的指针
2、如果必须分开定义指针和其所指向的对象,则将指针初始化为0.因为编译器可检测出0值的指针,程序可判断该指针并未指向一个对象。




void*指针:它可以保存任何类型对象的地址,不允许使用void*指针操作它所指向的对象。


指针操作:
如果对左操作数进行解引用,则修改的是指针所指对象的值;如果没有使用解引用操作,则修改的事指针本身的值。




引用与指针的重要区别:
1、引用总是指向某个对象,定义引用时没有初始化式错误的
2、赋值行为的差异:给引用赋值修改的事该引用所关联的对象的值,而并不是使用引用与另外一个对象关联。引用已经初始化,就始终指向同一对象。而对指针赋值,则是换了指针所指向的对象,不修改指针一开始所指对象的值。


指针的算术操作只有在原指针和计算出来的新指针都指向同一个数组的元素,或者指向该数组存储空间的下一个单元时才是合法的。


两个指向相减返回的类型时ptrdiff_t类型,它是signed类型,定义在cstddef头文件中。


C++中允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作。




指针和const限定符:
指向const对象的指针和const指针:
1、指向const对象的指针
如果指针指向const对象,则不允许使用指针来改变其所指的const值。为了保证这个特性,C++语言强制要求指向const对象的指针也必须有const特性。
const double *cptr;
const限定了所指向的对象类型,而并非cptr本身。也就是说,cptr本身并不是const。在定义时不需要对它进行初始化,如果需要的话,允许给cptr重新赋值,时期指向另一个const对象。但不能通过cptr修改其所指对象的值。
int i = 0;
cptr = &i; //ok
*cptr = 42; //error
把一个const对象的地址赋给一个普通的、非const对象的指针也会导致编译时的错误:
const double pi = 3.14;
double *ptr = π //error:ptr is a plain pointer
const double *cptr = π //ok: cptr is a pointer to const
不能使用void*指针保存const对象的地址,而必须使用const void*类型的指针保存const对象的地址。
允许把非const对象的地址赋给指向const对象的指针。不能用指向const对象的指针修改基础对象,然而如果该指针指向的是一个非const对象,可用其他方法修改其所指的对象。
double dval = 3.14;
const double *cptr = &dval;
dval = 3.14159; //OK
*cptr = 3.14159; //error;
double *ptr = &dval; //OK
*ptr = 2.72; //OK
cout << *cptr; //OK,prints 2.72


2、const指针——本身的值不能修改
int errNum = 0;
int *const currErr = &errNum;
从右向左读:currErr是指向int型对象的const指针。与其他const量一样,const指针的值不能修改(即使赋回同样的值),这就意味着currErr不能指向其他对象,必须在定义时初始化。
currErr = curErr;//error
指针本身是const的事实并没说明是否能使用该指针修改它所指向对象的值。指针所指对象的值能否修改完全取决于该对象的类型。
*currErr = 0; //OK




3.指向const对象的const指针
const double pi = 3.14159;
const double *const pi_str = &pi;
即不能修改指正所指对象(*pi_str的值不能改变),也不能修改指针的指向(pi_str值不能变)。




4、指针和typedef
string s;
typedef string *pstring;
const pstring cstr1 = &s; //与以下两种写法含义一样。因为声明const pstring时,const修饰的事pstring的类型,这回一个指针。因此,该声明语句应该是把cstr定义为指向string类型对象的const指针。
pstring const cstr2 = &s;
string *const cstr3 = &s;




C风格字符串:以null结束的字符数组
字符串字面值就是const char类型的数组
const char *cp = "some value";
while(*cp){ //遇到null结束,否则循环从cp指向的位置开始读书,直到遇到内存中某处null结束符为止。
++cp;
}




C风格字符串的标准库函数:
#include <cstring>


strlen(s) //返回s的长度,不包括结束符null
strcmp(s1,s2) //相等返回0,大于返回正数,小于负数
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




C++提供普通的关系操作符实现标准库类型的string的对象比较。这些操作符也可用于比较指向C风格字符串的指针,但效果不同:实际上,此时比较的是指针上存放的地址值,而并非他们所指向的字符串。如果比较的两个指针不指向同一个数组内的元素,则关系操作符实现没有定义。




创建动态数组:
C:malloc,free
C++:new,delete


int *pia = new int[10]; //10个空字符串的数组。动态分配数组时,如果数组元素具有类类型,将使用该类的默认构造函数实现初始化。
string *psa = new string[10]; //10个未初始化整形的数组。如果素组元素师内置类型,则无初始化


如果在自由储存区(堆)中创建的数组存储了内置类型的const对象,则必须为这个数组提供初始化:因为数组元素是const对象,无法赋值。唯一办法是初始化。
const int *pci_bad = new const int[100]; //error,没有初始化
const int *pci_ok = new const int[100](); //ok,初始化都是0的数组。
const string *pci = new const string[100]; //ok,允许定义类类型的const数组,只要该类类型提供默认构造函数




C++虽然不允许定义长度为0的数组变量,但明确指出,调用new动态创建长度为0的数组时合法的


动态空间的释放:
如果不在需要使用动态数组,必须显式的将其占用的存储空间返还给程序的自由存储区。
delete []表达式释放指针所指向的数组空间。


delete[] pia;//[]必不可少,他告诉编译器该指针指向的是数组,而不是单个对象.






新旧代码的兼容:
1、混合使用标准库类string和C风格字符串
由于C风格字串和字符串字面值具有相同的类型,而且都是以空字符null结束,因此可以把C风格字符串用在任何可以使用字符串字面值的地方
1)可以使用C风格字符串对string对象进行初始化
2)string类型的加法操作需要两个操作数,可以使用C风格字符串作为其中的一个操作数,也允许C风格字符串用作负荷赋值操作的右操作数。
反之则不成了:在要求C风格字符串的地方不能直接使用标准库的string类型对象,但提供了c_str()函数,返回c风格字符串,因为c_str返回的指针指向const char类型数组,所以以下第二式初始化失败,这样做是为了避免修改该数组。
如: char *str = st2; //error
char *str = st2.c_str();//almost ok,but not quite
const char *str = st2.c_str(); //OK,c.但c_str返回的数组并不保证一定有效,接下来对st2的操作有可能会改变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);//传递给ivec的两个指针标出了vector初值的范围。第二个指针指向被复制的最后一个元素之后的地址空间。
vector<int> ivec(int_arr+1,int_arr+4); //初始化创建了含有三个元素的ivec,三个元素的值分别是int_arr[1]到int_arr[2]的副本。




多维数组:
指针和多维数组:使用多维数组名时,实际上将其自动转化为指向该数组第一个元素的指针。
int ia[3][4];
int (*ip)[4] = ia; //ip points to an arrat of 4 ints,“()”不能少
ip = &ia[2]; //ia[2] is an array of 4 ints


int *ip[4]; //指针数组
int (*ip)[4]; //数组指针




用typedef简化指向多维数组的指针:
typedef int int_array[4];
int_array *p = ia;








第五章:表达式
算术操作符:
一元操作符+(正),-(负)优先级最高,其次是二元*(乘),/(除),%(求余或者求模),最后是二元+(加),-(减)
如果两个操作符都为正,则/和%的结果都为正;如果连个操作数都为负,则/的结果为正,而%的结果为负。如果只有一个操作数为负,则/为负,而%取决于机器


关系操作符和逻辑操作符:
优先级:
(!逻辑非),(<,<=,>,>=),(==,!=),(&&逻辑与),(||逻辑或)






位操作符:
(~,求反),(<<,>> 左移,右移),(&,位与),(^ 位异或),(| 或)




赋值操作符:左操作数必须是非const的左值;数组名是不可修改的左值,也不可以用作赋值操作符。
赋值操作符右结合性:赋值操作符返回左值,int ival,jval;ival = jval = 0;


自增自检操作符:
*iter++ 等效于 *(iter++)




箭头操作符:
(*p).foo; 等效于 p -> foo;


条件操作符:
cond?expr1:xpr2; //cond条件,expr表达式


sizeof操作符:
返回一个对象或类型名的长度,返回值的类型为size_t,长度的单位为字节。
用法:
sizeof(type name);
sizeof(expr);
sizeof expr;
将sizeof用于表达式,将获得表达式的结果作为类型的长度,但不计算出表达式,
如*p; sizeof(*p);//指针p可以是一个无效指针


sizeof的结果部分地依赖所涉及的类型:
1、对char类型或者为char类型的表达式做sizeof操作保证得1
2、对引用类型做sizeof操作将返回存放此引用类型对象所需的内存空间大小
3、对指针做sizeof操作将返回存放指针所需的内存大小;注意,如果要获取该指针所指向对象的大小,则必须对该指针进行解引操作
4、对数组做sizeof操作等效于将其元素类型做sizeof操作后乘上数组元素的个数




复合表达式的求值:


记忆方法:
--摘自《C语言程序设计实用问答》
问题:如何记住运算符的15种优先级和结合性?
解答:C语言中运算符种类比较繁多,优先级有15种,结合性有两种。
如何记忆两种结合性和15种优先级?下面讲述一种记忆方法。
结合性有两种,一种是自左至右,另一种是自右至左,大部分运算符的结合性是自左至右,只有单目运算符、三目运算符的赋值运算符的结合性自右至左。
优先级有15种。记忆方法如下:
记住一个最高的:构造类型的元素或成员以及小括号。
记住一个最低的:逗号运算符。
剩余的是一、二、三、赋值。
意思是单目、双目、三目和赋值运算符。
在诸多运算符中,又分为:
算术、关系、逻辑。
两种位操作运算符中,移位运算符在算术运算符后边,逻辑位运算符在逻辑运算符的前面。再细分如下:
算术运算符分 *,/,%高于+,-。
关系运算符中,〉,〉=,<,<=高于==,!=。
逻辑运算符中,除了逻辑求反(!)是单目外,逻辑与(&&)高于逻辑或(||)。
逻辑位运算符中,除了逻辑按位求反(~)外,按位与(&)高于按位半加(^),高于按位或(|)。
这样就将15种优先级都记住了,再将记忆方法总结如下:
去掉一个最高的,去掉一个最低的,剩下的是一、二、三、赋值。双目运算符中,顺序为算术、关系和逻辑,移位和逻辑位插入其中。










new和delete表达式:除动态创建和释放数组,这两个表达式也可用于动态创建和释放单个对象。
int *pi = new int; //pi points to dynamically allocated,unnamed,uninitilized int
1、动态创建对象的初始化
int *pi =new int(1024);
string *ps = new string(10,'9');
2、动态创建对象的默认初始化
如果不提供显示初始化,动态创建的对象一在函数内定义的变量初始化方式相同。对于类类型的对象,用该类的默认构造函数初始化,而内置类型的对象则无初始化。
string *ps = new string; //initialized to empty string
int *ps = new int; //pi points to uninitilized int


string *ps = new string(); //initialized to empty string
int *pi = new int(); //pi points to an int value-initilized to 0;




零值指针的删除:C++保证,删除0值的指针时安全的。
int *p = 0;
delete p; //OK


在delete之后,重设指针的值:
删除指针后,该指针编程悬垂指针,悬垂指针指向曾经存在的对象的内存,但该对象已经不再存在了。
一旦删除了指针所指向的对象,立即将指针置为0,这样就非常清楚的表明指针不在指向任何对象。


const对象的动态分配和回收:
const int *pic = new const int(1024);
const string *pcs = new const string;
动态创建的const对象必须在创建时初始化,且一经初始化,其值就不能改变。


动态内存的管理容易出错:
1、删除指向动态内存分配内存的指针失败,因而无法将该内存返还给自由存储区。删除动态内存失败称为“内存泄露”。
怎么检查内才能泄漏?检查内存泄漏的工具?
2、读写已经删除的对象。如果删除指针所指向的对象之后,将指针置为0值,则比较容易检查处这类错误。
3、对同一个内存空间使用了两次delete表达式。


删除const对象:
delete pci; //OK






类型转化:
int ival = 0;
ival = 2.51 +3;//先将3转化为double与2.51相加,得到5.51,在转化为int型得到5


隐式转化:
1、在混合类型表达式中 //int ival;double dval;ival >= dval;//ival转化为double
2、用作条件的表达式被转化为bool类型 //int ival;if(ival); while(cin)
3、用一表达式初始化或赋值给某个变量 //int ival = 3.14; int *ip = 0;//0转化成null指针




算术转化:
对于所有比int小的整形,包括char,bool,signed char,unsigned char,short,unsigned short,如果该类型的所有可能的值都包容在int内,他们就会被提升int型,否则,他们将会被提升为unsigned int。


显示转化:强制类型转化
static_cast,dynamic_cast,const_cast,reinterpret_cast
一般形式:cast_name<type>(expression)
dynamic_cast支持运行时识别指针或引用所指向的对象。


const_cast:将转化掉表达式的const性质。


static_cast:编译器隐式执行的任何类型转化都可以由static_cast显示完成。
double d = 97.0;
char ch =static_cast<char>(d);
如果编译器不提供自动转换,使用static_cast来执行类型转换也是很有用的。例如,下面的程序使用static_cast找回存放在void*指针中的值
void* p =&d;
double *db = static_cast<double*>(p); //可通过static_cast将存放在void*中的指针值强制转换为原来的指针类型,此时我们确保保持该指针值。也就是说强制转换的结果应与原来的地址值相等。


reinterpret_cast:通常为操作数的为模式提供较低层次的重新解释
如:
int *ip;
char *pc = reinterpret_cast<char*>(ip);


去const属性用const_cast。
基本类型转换用static_cast。
多态类之间的类型转换用daynamic_cast。
不同类型的指针类型转换用reinterpreter_cast。










第六章:语句
try块和异常处理
throw表达式:错误检测部分使用这种表达式来说明遇到了不可处理的错误。可以说,throw引发了异常条件
try块:错误处理部分使用它来处理异常。try语句块以try关键字开始,并以一个或多个catch子句结束。在try块中执行的代码所抛出的异常,通常会被其中一个catch子句处理。
由标准库定义的一组异常类,用来在throw和相应的catch之间传递有关错误信息


try
{
//程序中抛出异常
throw value;
}
catch(valuetype v)
{
//例外处理程序段
}
语法小结:throw抛出值,catch接受,当然,throw必须在“try语句块”中才有效。




寻找处理代码的过程与函数调用链刚好相反。抛出一个异常时,首先要搜索的事抛出异常的函数。如果没有找到匹配的catch,则终止这个函数的执行,并在调用这个函数的函数中寻找相匹配的catch。如果仍然买没有找到想对应的处理代码,该函数同样要终止,搜索调用它的函数。如此类推,继续按执行路径退回,知道找到适合类型的catch为止。
如果不存在处理该异常的catch子句,程序的运行就要跳转到名为terminate的标准库函数,该函数在exception头文件中定义。这个函数的行为依赖于系统,通常它的执行将导致程序的非正常退出。
在程序中出现的异常,如果没有经try块定义,则都已相同的方式来处理。即如果发生异常,系统将自动调用terminate终止程序的执行。




标准异常:
(1)exception头文件定义了最常见的异常类,它的类名是exception。这个类值通知异常的产生,但不会提供更多的信息
(2)stdexcept头文件定义了几种常见的异常类
(3)new头文件定义了bad_alloc异常类型,提供因无法分配内存而由new抛出的异常
(4)type_info头文件定义了bad_cast异常类型


标准异常类:
标准异常类值提供很少的操作,包括创建、复制异常类型对象以及异常类型对象的赋值。exception、bad_alloc以及bad_cast类型只定义了默认构造函数,无法再创建这些类型的对象时为它们提供初值。其他的异常类型则值定义了一个使用string初始化的构造函数。当需要定义这些异常类型的对象时,必须提供一个string参数。string初始化用于为所发生的错误提供更多的信息。
异常类型之定义了一个名为what的函数,不许任何参数,返回const char*类型的值。what函数所返回的指针指向C风格字符数组的内容,这个数组的内容依赖于一场对象的类型。对于接受string初始化式的异常类型,what返回该string作为C风格字符串数组。对于其他,返回值根据编译器而不同。




使用预处理器进行调试:
程序所包含的调试代码仅在开发过程中执行,当应用程序已经完成,并且准备提交时,就会将调试代码关闭。可使用NDEBUG预处理变量实现。
int main(){
#ifndef NDEBUG
cerr <<"starting main"<< ednl;
#endif
}
如果NDEBUG未定义,那么程序就会将信息写到cerr中。如果定义了NDEBUG,那么程序会跳过#ifndef和#endif之间的代码。
默认情况下,NDEBUG未定义,这也就意味着必须执行#ifndef和#endif之间的代码。在开发成型过程中,只有保持NDEBUG未定义就会执行这些调试语句。开发完成后,要将程序交给客户时,可通过定义NDEBUG预处理变量,删除这些调试语句。大多数编译器都提供定义NDEBUG的命令行选项:
$ CC -DNDEBUG main.c //这样的命令等效于在main.c的开头提供了#define NDEBUG预处理命令
预处理器还定义了其余四种在调试时非常有用的常量:
__FILE__ 文件名
__LINE__当前行号
__TIME__文件被编译的时间
__DATE__文件被编译的日期




另一个常见的调试技术是使用NDEBUG预处理变量以及assert(断言)预处理宏。assert宏在cassert头文件中定义的,所以使用assert的文件都必须包含这个头文件。
预处理宏有点像函数调用。assert宏需要一个表达式作为他的条件:
assert(expr)
只要NDEBUG未定义,assert宏就求解表达式expr,如果为false,assert输出信息并且终止程序的执行,如果该表达式有一个非0值,则assert不做任何操作。
与异常处理不同,程序员使用assert来测试“不可能发生”的条件。一旦开发和测试工作完成,程序就已经建立好,并且定义好了NDEBUG。在成品代码中,assert语句不做任何工作,因此没有任何运行时的代价。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics