/*Version 1.0 Author:Yleisin*/
1.字符串字面量 与 字符字面量 如"hello,world!" literal--常量
C++里没有内置的字符串数据类型,以数组的方式来支持字符串
2.continue将立即开始循环的下一次迭代,break将立即结束一个循环
3.1 C++中对数组不作边界检查,数组边界溢出将靠程序员编程是掌握,没有其他保护措施
如定义有数组 int a[10]
当以下标10(a[10])访问数组时,可能不报错而使程序崩溃
注:错误检测经常极大的降低程序执行效率
数组在内存中占总字节数=数组类型所占字节数*元素个数
3.1 字符串--以空字符结尾的字符数组,每个字符可以单独访问 '/0'表示空字符,值为0;
字符数组的大小=数组中所能容纳的最大字符个数+1
输入字符串,空字符包括空格、制表符和换行符,
cin<<str;//if str is " this is a test!",show str is "this"
gets(str);//read all the input
3.2 字符串函数
strcpy(to,from);
strcat(s1,s2); //output s1s2
strcmp(s1,s2); //if equal,return 0
strlen(s);//输出由s指向的字符串的长度,不包括字符串终止符 :char str="see you"; =>strlen(s)=7
3.3 字符串数组是一种特殊的二维字符数组
char str_array[10][20];//10 strings,each one can hold 20 chars
用来处理信息表,如雇员信息 :char name[10][80];//10 people,each one's name can hold 80 chars
4.排序 数组排序,快速排序--效率,效率!qsort();
5. 指针
指针的基本类型决定了编译器假定指针所指向的数据类型,如32位中 int *p;指向4个字节的内存地址
指针的数学运算:int *p; p=2000(address) =>p++=2004
int *p1; p=2000(address) =>p--=1996 32位环境中
p1=p1+9; //将p1指向当前位置后面的第九个位置
----------
int *p;
char str[20]="hello,jacky";
p=str;
p[i] equals *(p+i) //p[1]='e',*(p+1)='e'
----------------------------------------------------------
字符串表是由编译器生成的表,它用来保存程序中用到的字符串。
char *s;
s="pointers are fun to use.";
cout<<s;//output "pointes are fun to use."
-----
char *fortunes[]={
"0.you are right!",
"1.welcome you here!",
"2.now is your good time!",
"3.a close friend will ask for a favor!",
"4.we always love you!"
};
输出第二条信息,可以用下面的语句:cout<<fortuens[1];
----------------------------------------------------------------------------11/1/2008
6.未初始化的指针:使用指针之前保证指针指向一个有效的地方,包括指向空地址。
无效的指针比较
忘记复位指针
注:安全使用指针的关键在于,任何时候都需要知道指针指向的是什么位置。
7.(1)局部变量在执行到要使用时才被创建,在退出代码块后被销毁。局部变量在函数调用之间不能保存值。局部变量存储在栈上。
(2)全局变量在程序的整个执行期间都占据着内存,局部变量在需要使用的时候才分配内存。
8.传递指针和数组:
(1)将指针传递给数组 当在函数中对传递进来的指针进行运算时,实际上也是在对指针所指向的变量进行运算。因此,函数能够改变指
针所指变量的值。
(2)数组作为函数参数时,传递给函数的实际参数是数组的第一个元素的地址,而不是整个数组的副本。不带下标的数组名字也是指向数组
第一个元素地址的指针。
(3)可以将数组声明为指针 所有指针都可以像数组那样使用[]进行索引。
9.传递字符串:将一个字符串传递给函数时,实际上只是将字符串其首位置的指针传递给了函数,并且这个指针的类型是char *。
10.将字符串形式的数值转换为数字数值,通过命令行参数将数值传递给程序时,经常用到。
atoi(); //转换为整数形式
atol(); //转换为长整数形式
atof(); //转换为双精度浮点形式的数值
11.return语句;void函数
返回指针的函数-- int *f();//返回类型被声明为指向整数的指针
12.函数原型3个要素:返回类型、参数类型、参数的个数 --int max(int a,int b);
注:C++中必须在使用函数之前声明函数,C中可自由选择。
----------------------------------------------------------------------------------11/2/2008
函数:引用,重载,默认参数
13.函数重载是C++支持多态的一种形式
(1)两种参数传递的方法:
值调用--将实际参数的值传递给函数 函数中所进行的运算将不会影响到用来调用函数的实际参数
引用调用--将实际参数的地址传递给函数
引用参数--当使用引用参数时,实际参数的地址被自动传递给了函数
(2)函数重载:重载允许两个相关函数共用相同名字的机制。编译时根据参数类型和参数个数以及返回值类型正确调用重载函数。
overload:
overrid:
重载函数与歧义性:产生歧义性的主要原因是C++的自动类型转换--特定的调用引起了歧义性;另一个原因是重载中使用了默认参数
例:
#include <iostream>
using namespace std;
float myfunc(float i);
double myfunc(double i);
int main()
{
cout<<myfunc(10.1)<<" ";//没有歧义,调用myfunc(double)
cout<<myfunc(10);//有歧义
}
---------------------------------------------------------------------------------11/4/2008
更多的数据类型与运算符
14.const & volatile
const限定符可以防止程序修改某个变量;可由程序外部事件引起 const double version=3.2;//version info cannot be changed
15.存储类型限定符extern
extern:在C++中,每个全局变量都只能有一个副本;当程序由两个或多个文件组成时,可以在一个文件中声明全局变量,在其他文件中使用
extern来声明所需要的全局变量
static局部变量可以在函数调用之间保存值--只有声明static局部变量的函数或代码块才能知道这个局部变量。//static int count;
只有声明static全局变量的文件才能知道这个变量
register限定符要求编译器优化变量的访问速度--具体由编译器决定
16.枚举:关键字enum用来声明枚举
enum name{jacky,beyond,yleisin,franky,andy};
name hk;
hk=jacky;//equals to hk=0;
hk=beyond;//equals to hk=1;
17.typedef:可以使用typedef为已有的数据类型创建一个新名字,原来的名字并没有消失
18.按位运算符:运算对象是单独的位。
&:all 1 then 1
|:hava a 1 then 1
^:different then 1
>>:right shift 1 bit means value/2; <<:left shift 1 bit means value*2;
19.sizeof:编译时运算符,获取一个类型或值在内存中的大小。sizeof保证可移植性。
strlen(); //函数,获取字符串的长度:遇到null或'/0'之前的字符个数。
-------------------
但是,无论如何strlen()都是一个函数,它返回的是一个字符串的长度,也就是说从你给的字符串
首地址开始,一直到'/0'为止的这一段长度。
strlen 返回的是实际串长
sizeof 如果*str是指针则返回 char *的大小 如果是用数组声明 返回的是空间的大小
char *sz = "abcde";
char sz1[20] = "abcde";
cout<<sizeof(sz)<<endl;
cout<<strlen(sz)<<endl;
cout<<sizeof(sz1)<<endl;
cout<<strlen(sz1)<<endl;
输出:
4
5
20
5
---------------------
20.【c++:new & delete --算符】 VS 【c:malloc() & free() --函数】
(1)new & delete 动态分配:程序在执行期间获得内存的方法
new:分配动态内存 pointer-var = new var-type;
int *p;p=new int;//运算符new将分配足够的内存空间来容纳int类型的值并返回一个指向这个内存的指针
delete:释放先前被动态分配的内存 delete p;//释放内存
(2)初始化:int *p;p=new int(99);//用99初始化
分配数组的内存:pointer-var = new type [size]; delete [] pointer-var;
-----------------------------------------------------------
21.结构与联合
(1)结构是一组相关的变量,结构成员是结构中的一个变量,结构的名字是这种结构的类型限定符,struct是声明结构的关键字
struct inv_type{
char item[20]; //20 bytes
double cost; //8 bytes
double retail; //8 bytes
int on_hand; //4 bytes
int lead_time; //4 bytes
};
inv_type inv_var;
sizeof(inv_var)=44(20+8+8+4+4);
(2)访问结构成员:点运算符(.),如inv_var.cost
可以像声明其他指针变量一样来声明结构指针:在结构变量的名字前加一个*号。如上 inv_type *inv_var; 箭头运算符(->)通过指针
访问结构成员
(3)结构与类:结构中的成员默认是公有,类中成员默认为私有
(4)联合由两个或者多个变量组成,并且这些变量共用相同的内存地址 union是声明联合的关键字
union inv_type{
char item[20]; //20 bytes
double cost; //8 bytes
double retail; //8 bytes
int on_hand; //4 bytes
int lead_time; //4 bytes
};
inv_type inv_var;
sizeof(inv_var)=20;
-------------------------------------------------------
22.类
(1)构造函数:在创建对象时调用的函数。析构函数:在函数退出时调用,参数不能传递给析构函数。
(2)初始化构造函数:在声明对象时给对象一个参数。1)queue a=queue(101); 2)queue a(101); 1)等价于2)
(3)内联函数:一个小型函数,函数的代码被展开而不是被调用。
(4)对象数组、对象指针,对象引用
#include <iostream>
using namespace std;
class P_example{
int num;
public:
void set_num(int val) {num=val;}
void show_num();
};
void P_example::show_num()
{
cout<<num<<"/n";
}
int main()
{
P_example ob[2],*p; //定义了一个对象数组,包含两个对象;以及一个对象指针
ob[0].set_num(10); //第一个对象赋值为10;
ob[1].set_num(20);
p=&ob[0]; //获取数组第一个元素的指针
p->show_num(); //通过指针显示对象ob[0]的值
p++; //获取数组第二个元素的指针
p->show_num();
p--;
p->show_num();
return 0;
}
(5)友员函数:把一个非成员函数声明为类的友员,那么这个函数就可以访问类的私有成员。将函数的原型放在类声明的public部分,并在函
数声明的前面加上friend关键字。
(6)重载构造函数:在类中声明构造函数的不同函数原型并且定义相关的函数体。必须确定哪些构造函数是主要的,哪些是次要的。
(7)对象赋值:如果两个对象是同一种类型(即两个对象由同一个类声明),那么其中的一个对象可以被赋值给另一个对象。
默认情况下,一个对象的所有数据将通过按位复制被赋值给另外一个对象。也就是创建了一个完全相通的副本。
class myclass{
int i;
};
int main()
{
myclass ob1,ob2;
ob1.i=10;
ob2=ob1;
cout<<ob2.i;//输出10
}
---------------------------
(8)将对象传递给函数:传递的是对象的副本,而不是对象本身。
潜在问题:
1)传递对象给函数时,创建了对象的副本,此时是否调用的对象的构造函数?
普通的构造函数没有被调用,所调用的构造函数是按位复制的默认复制构造函数。
2)销毁对象时,是否调用了对象的析构函数?
副本被销毁时,析构函数被调用。
3)如果在用作实际参数的对象中分配了动态内存,并且在销毁该对象时释放内存,那么函数中实际参数的副本在调用析构函数时也将释放同样
的内存。但是原始的实际参数对象仍然在使用这块内存,这将破坏原始对象并且使对象变得不可用。
(9)返回对象:首先将函数的返回类型声明为一个类;其实,用return语句返回该类的一个对象。
潜在问题:
1)函数返回对象时,函数创建了一个临时对象来保存将要返回的值,返回的对象实际上是这个临时对象。当对象的值被返回后,临时对象将被
销毁。
2)如果函数所返回对象的析构函数释放被动态分配的内存,那么即使接收返回对象的对象依然要使用这块内存,而内存任然被释放。
-------------------------------------------------------
(10)解决(8)、(9)中的问题:创建和使用复制构造函数
问题进一步分析:与对象完全一样的对象副本并不是我们想要的。例如,如果对象包含了一个指针,指向一块已经分配了的内存地址,那么对
象副本中对应的指针也将指向同样的内存。因此,当对象副本改变了内存的内容,那么将会影响到初始化对象!而且,当函数结束时,对象副
本将被销毁,因此对象副本中的析构函数将被调用,这也可能对初始化对象产生不可预期的副作用。
1)复制构造函数:允许精确的控制创建对象副本时发生的行为。复制构造函数只有在初始化对象时才被调用。
2)当使用一个对象初始化另一个对象时,将会调用复制构造函数。
-------------------
#include <iostream>
#include <cstdlib>
using namespace std;
class myclass{
int *p;
public:
myclass(int i); //普通构造函数
myclass(const myclass &ob); //复制构造函数
~myclass();
int getval() {return *p;}
};
myclass::myclass(int i)
{
cout<<"allocating p/n";
p=new int;
*p=i;
}
myclass::myclass(const myclass &obj)
{
p=new int;
*p=*obj.p; //值的复制
cout<<"copy constructor called./n";
}
myclass::~myclass()
{
cout<<"freeing p./n";
delete p;
}
void display(myclass ob) //函数带有一个对象参数
{
cout<<ob.getval()<<'/n';
}
int main()
{
myclass a(10); //在函数main()中创建对象a的时候,普通构造函数为对象分配了内存并将内存地址赋给了变量a.p。
display(a); //当对象a被作为参数传递给display()中的ob时,对象a的复制构造函数将被调用,从而创建了对象a的一个副本。
//复制构造函数为对象的副本分配内存,并且将这块内存地址赋给对象副本的成员p。这样,变量a.p和ob.p所指向
的内存空间将是不同的和相互独立的,
//但是内存中包含的值是一样的。
return 0;
}
/*
//删除上面的display()的声明和定义,主函数做如下改动
int main()
{
myclass a(10);
myclass b=a; //使用对象a来初始化对象b,b的复制构造函数将被调用
return 0;
}
*/
---------------
3)当使用一个对象来初始化另一个对象时,也将调用复制构造函数。(上例红色部分)
4)当创建临时对象作为函数的返回结果时,复制构造函数也将被调用。
-------------------
#include <iostream>
#include <cstdlib>
using namespace std;
class myclass{
public:
myclass(int i); //普通构造函数
myclass(const myclass &ob); //复制构造函数
};
myclass::myclass(int i)
{
cout<<"allocating p/n";
}
myclass::myclass(const myclass &obj)
{
cout<<"copy constructor called./n";
}
myclass f()
{
myclass ob; //调用普通构造函数
return ob; //隐式地调用复制构造函数
}
int main()
{
myclass a; //第一次调用普通构造函数
a=f(); //第二次调用普通构造函数,并且调用复制构造函数
return 0;
}
--------------
5)C#,JAVA没有复制构造函数,而是使用按位复制的方法。
因为它们都是动态分配所有的对象,只能通过引用操作这些对象--因而将对象作为参数传递给函数或者从函数中返回对象时将不会创建对象
副本。
------------------
(11)this关键字
this是一个指针,指向调用成员函数的对象。由于友员函数不是类的成员函数,故友员函数中没有this指针。
-------------------------------------------------------------------
23.运算符的重载
-------------------------------------------------------------------
24.继承
(1)基类的访问控制 public,private,protected
1)当基类以public形式被继承时,它的公有成员将成为派生类的公有成员,私有成员仍将保持为基类的私有成员,不能被派生类访问。
2)当基类以private形式被继承时,它的公有成员和保护成员都将成为派生类的私有成员。
3)访问控制符protected用来声明类的保护成员: 此时于private一样,只有类中的成员函数可以访问它。
或者以protected形式继承基类: 基类中的公有成员和保护成员都将成为派生类的保护成员,在派生类中可以访问它们。
(2)多重继承:派生类可以从多个基类中继承,必须使用逗号将基类分隔,并且需要为每个基类指定访问控制符。
(3)构造函数、析构函数和继承
构造函数以派生的顺序进行调用,析构函数以相反的顺序进行调用。
(4)虚基类:当一个类从多个基类中继承时可能带来歧义性
----------------------
1)一个有歧义性的例子
class base{
public:
int i;
};
class derived1:public base
{
public:
int j;
};
class derived2:public base
{
public:
int k;
};
//derived3从derived1和derived2继承,这也意味着derived3中有两个base的副本
class derived3:public derived1,public derived2
{
public:
int sum;
};
int main()
{
derived3 ob;
ob.i=10; //'derived3::i' is ambiguous
ob.j=20;
ob.k=30;
ob.sum=ob.i+ob.j+ob.k; //'i' is ambigous
cout<<ob.i<<" "; //'i' is ambigous
cout<<ob.j<<" "<<ob.k<<" ";
cout<<ob.sum;
return 0;
}
---------------------------
2)以虚方式继承基类,可以确保在派生类中只有基类的一个副本。
class base{
public:
int i;
};
class derived1:virtual public base
{
public:
int j;
};
class derived2:virtual public base
{
public:
int k;
};
//derived3从derived1和derived2继承,这意味着derived3中只有一个base的副本
class derived3:public derived1,public derived2
{
public:
int sum;
};
int main()
{
derived3 ob;
ob.i=10; //'derived3::i' is clear
ob.j=20;
ob.k=30;
ob.sum=ob.i+ob.j+ob.k; //'i' is clear
cout<<ob.i<<" "; //'i' is clear
cout<<ob.j<<" "<<ob.k<<" ";
cout<<ob.sum;
return 0;
}
---------------------------
25.虚函数与多态
多态指使用相同的函数名来访问函数不同实现的方法。“一种接口,多种实现”。C++支持编译时多态和运行时多态。
运算符重载和函数重载是编译时多态的一种表现。还使用派生类和虚函数来实现运行时多态。
(1)指向派生类型的指针:运行时多态的基础是基类型指针,基类型指针可以指向其派生类的对象。
(2)虚函数:指在基类中使用virtual声明,并且在一个或者多个派生类中被重新定义的函数。每个派生类都可以拥有自己的虚函数定义。
定义了虚函数的类被成为多态类。
---------------
#include <iostream>
#include <cstdlib>
using namespace std;
class base
{
public:
virtual void who(){cout<<"base/n";}
};
class first_d:public base
{
public:
void who(){cout<<"first derived/n";}
};
class second_d:public base
{
public:
void who(){cout<<"second derived/n";}
};
int main()
{
base base_obj;
base *p;
first_d first_obj;
second_d second_obj;
p=&base_obj;
p->who();
p=&first_obj;
p->who();
p=&second_obj;
p->who();
return 0;
}
-------------
1)函数是在运行时决定调用虚函数的哪个定义,这个决定只依赖于基类型指针所指向的对象的类型。
2)在派生类中重新定义虚函数时,被成为覆盖虚函数--overriding 【overloading--重载】
附:【overloading--重载】VS 【overriding--覆盖】
1)重载函数必须在参数的类型或者数量上不同,而重新定义的虚函数在参数类型和参数数量上必须相通。虚函数的原型与它重新定义的
形式必须完全相通。
2)在定义虚函数的类中,虚函数必须声明为类的成员而不能是友员。但虚函数可以被声明为其他类的友员。
(3)为什么需要虚函数
1)基类声明所有派生类的公告接口,而让派生类定义具体的方法来实现这些接口。--“一种接口,多种方法”
2)基类和派生类形成了一种层次结构:基类可以定义所有派生类都使用的通用接口;派生类能够在保证一致接口的同时,还能够定义自
己的方法。
(4)纯虚函数:在类中没有定义的虚函数。这将强制派生类必须有自己的虚函数定义,不能使用基类中的虚函数,否则编译将报错。
至少包含一个纯虚函数的类被成为抽象类。
(5)早绑定:指函数的调用在编译时就确定了,有点是运行更快,需要更少的内存,缺点是缺少灵活性。
晚绑定:函数调用在运行时确定。使用虚函数和派生类来实现。支持公共接口,可以重新定义函数,创建可被重用和扩展的类库。速度较
慢。