Thursday, April 19, 2007

C++学习

发现很多公司要求C++,虽然工作已经有一个了。但7月上班,实在难耐,所以最近想着试几家大一点的公司,看看有没有更好的机会。由于强项在Java,很多大公司都只能望而却步。为了第一份工作一搏!决定重拾C++,发一帖学习日记,一是标出一些重点、难懂的东东;二是记录学习历程;三是可以给google友们多一个找答案的选择。
学习的书是问陈胖子借的,虽然不喜欢中文书,但听说翻译还可以,而且赶时间(就两个月了呀),呵呵,就不计较了。工具是VS2005,-_-b,可能要被小青年鄙视了,因为我整天就唠叨不要用VS,要用GCC。但确实,就业需求所逼,改吧。

Question 1: const
const很复杂,跟指针和引用混在一起更复杂。先两个概念,常量指针和指常量的指针:
int a=0;
const int *p1=&a; //指常量的指针,此处a可以是常量也可以不是,但结果是(*p1)不能再修改
int *const p2=&a; //常量指针,不可以改变p的指向,功能类似引用(但有区别)
const int *const p3=&a; //指常量的常量指针,上面两个特点综合一下

谈到指针和引用,说一下区别,引用是引对象,而指针就是地址。举些例子:
double a=0.0;
const int &b=a; //其实是先 int tmp=a;然后 const int &b=tmp; 因为b要引一个int对象,所以临时创建了一个
const int &c=0; //其实是先 int tmp=0;然后 const int &c=tmp; 同上,先创建一个对象


常量只可以用指常量的指针指。另有书上一例:
int *const &p=a; // 经检验,效果等同 int *const p=a;


Question 2: 陌生关键字
volatile : 表示变量是易变的,Java中也有。但C++中的含义是,编译器不能随意对该变量进行优化处理。即变量在编译器无法察觉的情况下可能改变。
sizeof : sizeof本身并不陌生,只上他有一点比较特殊,,sizeof是编译时刻计算的,因此被看作是常量表达式。


Question 3: 常用STL类型
vector 首当其冲,最好的替代数组的类型。有数组式的使用习惯和STL式的使用习惯,不能随意互用啊:
// 数组习惯
vector<int> vct(10);
for(int i=0 ; i<vct.size() ; ++i)
vct[i]=i;

// STL 习惯
vector<int> vct;
for(int i=0 ; i<10; ++i)
vct.push_back(i);
for(vector<int>::iterator iter=vct.begin() ; iter!=vct.end() ; ++iter)
cout<< *iter << endl;

complex 复数类型,可以用complex<double> 或者 complex<long double>等。
pair 关联两种类型的值。
bitset 向量类型,用 bitset<32>构造向量长度为32。
list,deque,map/multimap, set/multiset
stack 栈,其中与一般栈不同之处是普通栈的 pop删除头并返回头元素,而stl栈只删除不返回,它使用top返回头。
queue和priority_queue,队列和优先队列,priority_queue在Java下满好用的 ^_^。

Question 4: 自己写类
写一个完整的类不容易啊,首先分为三个基本步骤(自己总结的,不知道对不对):
1 类定义,常定义在.h文件中
2 函数、变量定义,.cpp文件的部分
3 添加其他会使用到该类的其他类中的方法,典型的如 istream& operator >>( istream&,类&) 等。

在定义时也能定义部分类函数和变量,在类定义中定义的函数的都是缺省为inline的(使用频率高的,短小的代码可以考虑声明为inline)。在类外定义的需要显示声明inline。

类定义基本结构:

#include <iostream> //包含需要使用的头文件

class Sample; // 先声明一下类

// 声明一些会使用到该类的函数
istream& operator >( istream&, Sample&amp;);

// 类定义
class Sample
{
public:
Sample(); //默认构造函数及其他构造函数
Sample( const Sample&); //拷贝构造函数,如果需要

~Sample(); //析构

Sample& operator = (const Sample&amp;); //重载操作符

void setValue(int); //定义成员函数
private:
int _value; //定义成员变量
};

Question 5: 显式转换
新式(标准C++)强制类型转换: static_cast,dynamic_cast,const_cast,reinterpret_cast
任何非const数据类型的指针都可以被赋值给void* 指针。
const_cast< type >( expression ) 用于转换掉表达式的常量性 (以及volatile对象的volatile性)。
static_cast< type >( expression ) 编译器隐式执行的任何类型转换都可以由它显式完成。
reinterpret_cast< type >( expression) 对于操作数的位模式执行一个比较低层次的重新解释,它的正确性很大程度上依赖于程序员的主动管理。
dynamic_cast<type>( expression )支持运行时刻识别由指针或引用指向的对象。


旧式(标准C++前)强制类型转换:可以替代static_cast、const_cast、reinterpret_cast。

Question 6: 容器
抽象容器类型{
顺序容器(sequence container): list,vector,deque
关联容器(associative container):map,set
}

每个容器支持一组关系操作符,可以用来比较两个容器。第一个不相等元素的比较决定了两个容器的大小关系。
容器的类型有三个限制
  • 元素类型必须支持等于操作符
  • 元素类型必须支持小于操作符
  • 元素类型必须支持一个缺省值
所有预定义数据类型,包括指针,都满足这些限制,C++标准库给出的所有类型也一样。

Question 6: 迭代器
除了iterator类型,还定义了一个const_iterator类型,后者对于遍历const容器是必需的。 iterator算术运算只适用于vector或deque,而不适用于list。

Question 7: 函数
参数为数组,数组确实满麻烦的,首先数组参数绝不会传值,传的是指针。举以下五个例子,前三者相同:

void putValues(int*);
void putValues(int[]);
void putValues(int[ 10 ]);
void putValues(int (&arr)[10]); //参数为10个int的数组,因为参数是引用的,即数组长度成为参数的一部分
void putValues(int matrix[][10], int rowSize); // 第一维长度为rowSize,第二维长度是10
void putValues( vector<int> &vec ); //vector是替代数组的最好类型


指定函数的缺省实参:

int func1(int a,int b=0,int c=0);

int func2(int a,int b,int c=0); // 初始化最右边参数
int func2(int a,int b=0,int c); //可以!此时b c都有缺省参数
int func2(int a,int b=0,int c=0); //错误,bc已经有缺省参数

int func3(int a,int b,int c=func1(1)); //缺省实参可以是任意表达式

int func4(...); // ellipsis 省略号,告知编译器,函数调用时,可以有0或多个实参,而类型未知


命名返回值优化,易犯错误:
1 返回一个指向局部对象的引用。局部对象的生命周期随函数的结束而结束。

Matrix& func()
{
Matrix a;
return a; // 错误,结果将指向一个错误的位置
}



2 函数返回一个左值,对返回值的任何修改都将改变被返回的实际对象。

int& get_val( vector<int> &vi, int ix)
{
return vi[ix];
}

int ai[4] = {0,1,2,3};

vector vec(ai, ai+4);

int main()
{
get_val(vec,0)++;
}


正确使用命名返回值优化:

Matrix& get(Matrix *p)
{
Matrix *res = new Matrix(); //动态分配的,函数结束不会结束
*res=*p;
return *res;
}

函数指针(指向函数的指针类型):

int cmp1(const string &s1, const string &s2)
{
return 1;
}

int cmp2(const string &s1, const string &s2)
{
return 0;
}

int *pf(const string&, const string&amp;amp;amp;amp;amp;amp;); // 错误,返回类型指针
int (*pv)(const string&, const string&amp;amp;amp;amp;amp;amp;) = 0; //初始化,不指向任何东西
int (*pf)(const string&, const string&amp;amp;amp;amp;amp;amp;) = cmp1; //正确,pf 是指向函数的指针
pf=cmp2; //赋值,可以
pf=&cmp1; //可以,同上

cmp1("a","b");
pf("a","b");
(*pf)("a","b"); //三个效果相同,返回1


函数指针的数组(so...神奇):

int func1(const int &a,const int &amp;amp;amp;amp;amp;amp;b);
int func2(const int &a,const int &amp;amp;amp;amp;amp;amp;b);

typedef int (*PFV)(); //定义函数类型指针的typedef
PFV tc1[10]; // 函数指针数组长度为10
PFV tc2 = { func1, func2 }; // 初始化
tc1[0] = &func1; // 赋值
tc1[0](10,20); //调用
((*tc1)[0])(10,20); // 显式调用


函数指针可以作为返回类型,但是函数不能做为返回类型:

int (*func(int))(int, int); // ff为函数,有一个int参数,返回一个指向函数的指针,该指针类型为 int (*)(int, int);

typedef int (*PF)(int, int);
PF ff( int ); //更优雅

typedef int func(int, int);
func ff(int); // 错误,返回类型不能是函数类型



Question 8: extern
链接指示符 extern "C" / extern "Ada" / extern "FORTRAN"
告诉编译器,该函数是使用其他语言编写的:

extern "C" void exit(int);

extern "C"
{
int printf( const char* ...);
int scanf(const char* ...);
}

extern "C"
{
#include <cmath>
}

Question 9: 头文件
头文件为所有extern对象声明、函数声明以及inline函数定义提供了一个集中的位置。
预编译头文件而不是普通头文件可以大大降低应用程序的编译时间。
头文件不应该含有非inline函数或对象的定义。否则将可能使同一程序的两个或多个文件中包含,就会产生重复定义的编译错误。


Question 10: 域和生命期
局部对象:自动对象、寄存器对象、局部静态对象

自动对象地址不应该被用作函数的返回值,因为函数一旦结束了,该地址就指向一个无效的存储区。当一个自动变量的地址被存储在一个生命周期长于它的指针时,该指针被称为空悬指针(dangling pointer)。

寄存器自动对象:在函数中频繁被使用的自动变量可以用register声明。

for( register int ix=0; ix


静态局部对象:未初始化的静态局部对象会被程序自动初始化为0,相反,自动对象的值是任意的,除非它被显式初始化。

动态分配的对象:空闲存储区被耗尽时,new表达式失败,所以抛出一个bad_alloc异常。delete会调用操作符delete(), C++保证若指针被设置为0,则不会调用delete(),故之前没必要测试指针是否为0。但delete之后,并不会把指针赋值为0。

auto_ptr
auto_ptr可以帮助程序员自动管理用new表达式动态分配的单个对象,不支持数组。


#include
auto_ptr< type_pointed_to > identifier( ptr_allocated_bynew );
auto_ptr< type_pointed_to > identifier( auto_ptr_of_same_type );
auto_ptr< type_pointed_to > identifier;

auto_ptr< int > pi(new int(1024));

由于操作支持都是内联的,所以效率不比直接使用指针代价高。ptr相关操作:
reset: 初始化后,就只能用reset再对auto_ptr赋值了。
get: 返回auto_ptr对象内部的底层指针。
release:允许将一个auto_ptr对象的底层对象初始化或赋值给第二个对象,而不会使两个auto_ptr对象同时拥有同一对象的所有权。

定位new表达式:

new ( place_address ) type-specifier // 表达式格式

class Foo{};
char *buf = new char [ 1024 ];
Foo *pb = new ( buf ) Foo; //无须delete
delete[] buf;

No comments: