1、C++语言支持的新特性
注:1983年夏,“带类的C”被正式命名为“C++”
2、常量
数值常量、字符常量、符号常量、枚举常量。。。
3、变量
- 变量名代表内存中的一个存储单元,在程序编译连接时由系统给每一个变量分配一个地址。通过变量名找到相应的存储单元,从中读取数据
- 变量名的命名规则
- 未对变量赋初值,其值是一个不可预测的值
- 初始化不是在编译时完成的(静态存储变量和外部变量除外),而是在运行时执行本函数时赋予初值的
4、数据类型
- C++编译系统把十进制小数形式的浮点数默认按双精度处理
- 浮点数在内存中以指数形式存放
- 若一个字符串的最后一个为“\”,则它是续行符
- 在计算机中负数的运算和显示以补码的形式,16的原码为 0001 0000,~16为 1110 1111 ,则~16为负数。因此,~16补码为 1000 1010+1=1000 1011=-11
不同类型数据间的转换(赋值兼容):
- 标准类型数据间的转换
- 隐式转换;显示转换:类型名(数据) / (类型名)数据
- 用转换构造函数进行类型转换
- 用类型转换构造函数进行类型转换
下面为常量、变量以及数据类型的测试代码:
#include<iostream> using namespace std; #define PI 3.1415926 int main() { const int NUM=6; enum color{red,yellow,green,black,white}; cout<<green<<endl; int a;//未初始化,值不确定 cout<<"a="<<a<<endl; float b=1.23456789; cout<<b<<endl; long double c=1.23456789; cout<<c<<endl; cout<<sizeof(short int)<<"Byte"<<‘\t‘ <<sizeof(int)<<"Byte"<<‘\t‘ <<sizeof(float)<<"Byte"<<‘\t‘ <<sizeof(double)<<"Byte"<<‘\t‘ <<sizeof(long double)<<"Byte"<<endl; double d=1.23f; cout<<d<<endl; char ch=6; cout<<ch<<endl; char ch1=0141; //97 cout<<"ab\\\"cd\"\t\101\x41\0cd"<<‘\0‘<<ch1<<endl; return 0; }
5、指针
- 一个变量的地址即该变量的指针
- 指针变量的值是地址(指针)
- 凡是存放在内存中的程序和数据都有一个地址,可以用它们占用的那片存储单元中的第一个存储单元的地址表示
& 和 * 的使用:
有关指针的数据类型:
指针运算:
- 指针变量加减一个整型值
- 指针变量的赋值
- 指针变量可以赋空值,但不能赋整数
- 指向同一类型的指针变量相互赋值时,后期操作可能造成指针悬挂问题(一指针所指空间被释放,另一指针所指空间亦被释放),在释放一指针所指的空间时,一定要确保所有指向该空间的指针都已不再需要,以防止指针悬挂
- 若要对不同类型的指针变量赋值,应用强制类型转换
- 指针变量的比较
- 指针变量的相减,相加无意义
指针的一些注意事项:
- 所有指针,包括访问本地堆的指针,在 Win32 下都是 32 位(4个字节)
- 一般的C++编译系统为每一个指针变量分配4个字节的存储单元,用来存放变量的地址
- 在使用指针变量之前须先给它赋一个指向合法具体对象的地址值,否则可能破坏有用的单元的内容
指针的优缺点:
优点
- 在调用函数时,若改变被调用函数中某些变量的值,这些值能为主调函数使用,可通过函数的调用得到多个可改变的值
- 可以实现动态存储分配
- 占内存少,运算速度快
缺点
- 容易产生难以发现的错误
数组与指针:
指针数组作为main函数的形参:
在DOS状态下输入:命令名 参数….
下面为相应的代码:
#include<iostream> using namespace std; int main(int argc, char *argv[]) { while(argc > 1) { ++argv; cout<<*argv<<endl; --argc; } return 0; }
动态存储分配:
6、引用
- int &b = a; //声明b是a的引用(别名)
- 声明引用时必须使之初始化
- 声明b是a的引用后,b不能再作为其他变量的引用,且b和a占同一存储单元,具有同一地址(指针变量需另外开辟空间)
- 主要用来作为函数参数
- 调用函数时,实参是变量名不是地址,而系统向形参传递的是地址不是其值
7、字符串
- 对一个字符串常量,系统会在所有字符的后面加一个’\0’作为结束符
- 字符串的长度比对应的字符数组(包含一个空字符’\0’)少1字节,字符串数组每一个字符串元素不包含’\0’
- 编译系统为每一个字符串变量(string类的一个对象)分配固定长度的存储单元(VC为16个字节,GCC为4个字节),存放字符串的地址,即字符串的指针
访问字符串的三种方法:
- 字符数组,即C-string方法
- 字符串变量,即string方法(C++)
- 字符指针
指针、引用、字符串测试源代码:
#include<iostream> using namespace std; int main() { char str[] = "Hello"; string m_str = "Hello"; string name[5] = {"zhang","li","gao"}; char *pstr = "Hello"; while(*pstr!=‘\0‘) { cout<<*pstr; pstr++; } cout<<endl; cout<<"字符数组的长度:" <<sizeof(str) <<"Byte"<<endl; cout<<"字符串的长度: " <<strlen("Hello")<<"Byte"<<endl; //每一个字符串变量被编译系统分配固定长度的存储单元,VC++为16个字节 cout<<"字符串变量的长度:"<<sizeof(m_str) <<"Byte"<<endl; cout<<sizeof(string)<<‘\t‘<<sizeof(name)<<endl; //一般的C++编译系统为每一个指针变量分配4个字节的存储单元,用来存放变量的地址 cout<<sizeof(pstr)<<endl; cout<<"**************************************************"<<endl<<endl; int arr[10]={1,2,3,4,5,6,7,8,9,10}; int *p=arr; for(int i=0;i<10;i++) { // cout<<arr[i]<<‘\0‘; cout<<*(p+i)<<‘ ‘; } cout<<endl; int *p1 = new int(10); cout<<*p1<<endl; delete p1; return 0; }
8、函数
- 函数名代表函数的入口地址即函数的指针,函数的指针在编译时被分配
- 当数组作函数形参时,C++编译系统将形参数组名一律作为指针变量来处理
- 实参数组名代表一个固定的地址,或说是指针常量,而形参数组名为指针变量
- 根据函数能否被其他源文件调用,将函数区分为内部函数和外部函数
函数参数传递:
内置函数:
- 在编译时将调用的函数的代码直接嵌入到主调函数中,而不是将流程转出去,这种嵌入到主调函数中的函数称为内置函数(inline function),又称内嵌(内联)函数
- 在函数首行的左端加一个关键字inline,可在声明和定义时同时写,也可只在其中一处写,效果相同
- 可节省运行时间,但增加了目标程序的长度
- 只有那些规模小而又被频繁调用的简单函数,才适合声明为inline函数;而且对函数作inline声明对编译系统只是建议性的
函数重载、覆盖与隐藏:
9、枚举
enum weekday{sun, mon, tue, wed, thu, fri, sat};//声明 weekday workday, week_end; //定义枚举变量
在C++中允许不写enum(一般也不写),花括号中的称为枚举元素或枚举常量,C++编译按定义时的顺序对它们赋值为0,1,2,3,…
可在声明枚举类型时指定枚举元素的值:
enum weekday{sun=7, mon=1, tue, wed, thu, fri, sat};
编译系统自动按顺序加1,则sat为6
一个整数不能直接赋给一个枚举变量,应先强制类型转换,例:workday = weekday(2); //C++风格形式
10、共用体(联合)
11、结构体
12、位段(位域)
枚举、共用体、结构体、位段源代码:
#include<iostream> using namespace std; //声明枚举类型 enum weekday{sun=7, mon=1, tue, wed, thu, fri, sat}; struct Student //结构体 { int age; char name[20]; union P //共用体 { int grade; char position[10]; }category; unsigned a:2;//位段 unsigned b:3; }; int main() { weekday workday;//定义枚举变量 workday=weekday(1);//C++风格的强制类型转换格式 cout<<workday<<endl <<wed<<endl; Student person[2];//定义共用体变量 person[0].category.grade=20; cout<<person[0].category.grade<<endl; person[1].a = 3; //给位段赋值 cout<<"a="<<person[1].a<<endl; person[1].b = 12; //十进制12-->二进制1100--取其低3位-->二进制100-->十进制4 cout<<"b="<<person[1].b<<endl; return 0; }
13、类
- 类是对象的抽象,不占存储空间;对象是类的实例,占用存储空间
- 一个对象所占的空间的大小只取决于其中的数据成员所占的空间,而成员函数不占用对象的存储空间
- 信息隐蔽 = 类的公用接口与私有实现的分离
- 把类的声明(包含成员函数的声明)放到一个头文件中;把成员函数的定义放到另一文件中,单独编译,从而不重复编译
- 类库 = 类声明头文件 + 已编译过成员函数的定义的目标文件
类的声明与对象的定义:
成员对象的访问:
构造函数:
析构函数:
- ~Box( ){ };
- 析构函数与类同名,无返回值(类型),无参数
- 一个类只能有一个析构函数
- 当对象的生命周期结束时,自动执行析构函数
- 对一个对象来说,析构函数是最后一个被调用的成员函数
- 其作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作
- 调用析构函数的顺序与构造函数相反
对象指针:
类和对象源代码(一):
#include<iostream> using namespace std; class Box { int length, width, height;//私有成员 public: Box(); //用参数初始化表对数据成员初始化 Box(int len, int w, int h):length(len),width(w),height(h) { } ~Box() { } int volume(); };//分号不可少 Box::Box() { length = 5; width = 5; height = 5; } int Box::volume() { //显示的使用this指针 return this->width * this->height * this->length; } int main() { Box b1; Box *p = NULL;//指向对象的指针 //定义对象数组 Box b[3] = {Box(),Box(10,10,10),Box(20,20,20)}; p = &b[2]; cout<<b1.volume() <<endl; cout<<b[0].volume()<<‘\t‘ <<b[1].volume()<<endl; cout<<p->volume() <<‘\t‘ <<(*p).volume()<<endl; return 0; }
共用数据的保护:
静态成员:
对象的赋值与复制:
友元:
类和对象源代码(二):
#include<iostream> using namespace std; class Date;//对Date类的提前引用声明 class Time { public: Time(int, int, int); void display(Date &);//形参为对象的引用 private: int hour,minute,second; }; class Date { public: Date(); Date(int, int, int); //声明Time类中的display函数为Date类的友元成员函数 friend void Time::display(Date &); private: int year,month,day; }; Time::Time(int h, int m, int s):hour(h),minute(m),second(s) { } void Time::display(Date &d) { cout<<"d.day="<<d.day<<‘\t‘ <<"t.hour="<<hour<<endl; } Date::Date() { year = 1000; month = 12; day = 20; } Date::Date(int y, int m, int d) { year = y; month = m; day = d; } int main() { Time t(5,5,5); Time t1(t); //对象的复制 Date d(2000,10,30), d1; d1 = d; //对象的赋值 t.display(d); t1.display(d1); return 0; }
14、运算符重载
- 实质上是函数重载
- 形式:函数类型 operator运算符名称(形参表列){};
- C++不允许用户自己定义新的运算符,只能对已有的C++运算符进行重载
- 重载不能改变运算符运算对象的个数、优先级别、结合性,不能有默认的参数,参数至少有一个类对象(其引用)
- C++约定:若在前置自增(自减)运算符重载函数中增加一个int型形参,就是后置(自减)运算符函数
- 只能将重载“>>”和“<<”的函数作为友元函数或普通的函数,而不能将他们定义为成员函数
15、继承和派生
- 继承:一个新类从已有的类那里获得其已有特性
- 派生:从已有的类(基类)产生一个新的子类(派生类)
- 继承与组合:在一个类中以另一个类的对象作为数据成员
- 继承分单继承和多重继承,派生分一级派生和多级派生
- 继承方式:公用、保护、私有
- 访问属性:公用、保护、私有、不可访问
- 派生类的成员:基类的全部成员(不包括构造和析构函数)+自己增加的成员
- 私有成员只能被本类的成员函数访问,保护成员可被派生类的成员函数引用
- 类的成员在不同的作用域中有不同的访问属性
派生类的构造与析构函数:
- 派生类构造函数名(总参数表列):基类构造函数名(参数表列),…,子对象名(参数表列),… { 派生类新增数据成员的初始化语句 }
- 派生类构造函数的执行顺序:基类、子对象、派生类
- 多重继承中的初始化是按基类表中的说明顺序进行的
- 析构函数与构造函数相反
- 若基类未定义构造函数或定义了未带参数的构造函数,则在派生类中可不写基类构造函数;在调用派生类构造函数时系统会自动首先调用基类默认的构造函数
多重继承引起的二义性问题:
基类与派生类的转换:
继承与派生源代码:
#include<iostream> #include<string> using namespace std; class Person { public: Person(string nam,int a,char s):name(nam),age(a),sex(s) { } void show() { cout<<"name:" <<‘\t‘<<name <<endl <<"age:" <<‘\t‘<<age <<endl <<"sex:" <<‘\t‘<<sex <<endl; } protected: string name; int age; char sex; }; class Teacher:virtual public Person//声明Person为公用继承的虚基类 { public: //构造函数 Teacher(string nam,int a,char s,string t):Person(nam, a,s),title(t) { } void display() { show(); cout<<"title:"<<‘\t‘<<title<<‘\n‘; } protected: string title; }; class Student:virtual public Person//声明Person为公用继承的虚基类 { public: //构造函数 Student(string nam, int a,char s,float sco):Person(nam,a,s),score(sco) { } void display() { show(); cout<<"score:"<<‘\t‘<<score<<‘\n‘; } protected: float score; }; class Graduate:public Teacher,public Student//声明多重继承的派生类Graduate { public: //不仅要对直接基类初始化,还要对虚基类初始化 Graduate(string nam,int a,char s,string t,float sco,float w): Person(nam,a,s),Teacher(nam,a,s,t),Student(nam,a,s,sco),wage(w) { } void present() { cout<<"name:" <<‘\t‘<<name <<‘\n‘ <<"age:" <<‘\t‘<<age <<‘\n‘ <<"sex:" <<‘\t‘<<sex <<‘\n‘ <<"title:"<<‘\t‘<<title<<‘\n‘ <<"score:"<<‘\t‘<<score<<‘\n‘ <<"wage:" <<‘\t‘<<wage <<‘\n‘; } private: float wage; }; int main() { Graduate grad1("WangLi",24,‘f‘,"assistant",99.5,1325.5); grad1.present(); return 0; }
16、多态性和虚函数
- 多态性:用一个函数名调用具有不同内容(功能)的函数;不同对象对同一消息有不同的响应方式(一个接口,多种方法)
- 从系统实现的角度,多态性分为:静态多态性(编译时的多态性)和动态多态性(运行时的多态性,通过虚函数实现)
- 关联:调用具体的对象的过程(把一个函数名与一个对象捆绑在一起)
- 静态(早期)关联:通过对象名调用虚函数,在编译时即可确定其调用的函数属于哪个类
- 动态(滞后)关联:通过基类指针调用虚函数,在运行阶段把虚函数和类对象捆绑在一起
虚函数:
- 类外定义虚函数时不必加virtual
- 派生类中定义的同名函数:函数名、类型、参数个数、参数类型须与虚函数相同;当一个成员函数被声明为虚函数后,其派生类的同名函数都自动成为虚函数
- 先用基类对象指针指向同一类族中某一对象,即可调用该对象中的同名函数
- 函数重载处理的是同一层次上的同名函数问题,而虚函数处理的是不同派生层次上的同名函数问题(函数覆盖)
- 当一个类有虚函数时,编译系统会为该类构造一个虚函数表(一个指针数组),存放每个虚函数的入口地址
- 构造函数不能声明为虚函数;若基类的析构函数声明为虚函数,则其所有派生类的析构函数均为虚函数
纯虚函数与抽象类:
纯虚函数:virtual 函数类型 函数名(参数表列)=0;//声明语句
- 无函数体;“=0”并不表示返回值为0,只是形式;不具备函数的功能,不能被调用
- 若在派生类中未对该函数定义,则在派生类中仍为纯虚函数
抽象类:不用来定义对象,只作为一种基本类型用作继承的类
- 可定义指向抽象类对象的指针变量,通过指针调用具体派生类的虚函数
- 凡是包含纯虚函数的类都是抽象类,抽象基类是本类族的公共接口
多态性与虚函数源代码:
#include<iostream> #include<string> using namespace std; class Student { public: Student(string nam,int a,char s,float sco):name(nam),age(a),sex(s),score(sco) { } // virtual void display() = 0;//纯虚函数 virtual void display()//虚函数 { cout<<"name:" <<‘\t‘<<name <<‘\n‘ <<"age:" <<‘\t‘<<age <<‘\n‘ <<"sex:" <<‘\t‘<<sex <<‘\n‘ <<"score:"<<‘\t‘<<score<<‘\n‘; } protected: string name; int age; char sex; float score; }; class Graduate:public Student { public: Graduate(string nam,int a,char s,float sco,float w): Student(nam,a,s,sco),wage(w) { } void display()//虚函数 { cout<<"name:" <<‘\t‘<<name <<‘\n‘ <<"age:" <<‘\t‘<<age <<‘\n‘ <<"sex:" <<‘\t‘<<sex <<‘\n‘ <<"score:"<<‘\t‘<<score<<‘\n‘ <<"wage:" <<‘\t‘<<wage <<‘\n‘; } private: float wage; }; int main() { Student stud("LiGang",18,‘m‘,95); Graduate grad("WangLi",24,‘f‘,99.5,1325.5); grad.display();//静态关联 cout<<"******************************************\n"; Student *pt = &stud; pt->display();//动态关联 cout<<"******************************************\n"; pt = &grad; pt->display(); return 0; }
17、输入输出流
- 标准I/O:键盘输入数据,显示到屏幕上
- 文件I/O:以外存磁盘文件为输入输出对象
- 字符串I/O:对内存中指定的空间(通常为字符数组,利用该空间可存储任何信息)进行输入输出
标准输入输出:
- 标准输出流
- cout,cerr,clog流
- 格式输出
- 使用控制符控制输出格式:头文件iomanip
- 使用流对象的成员函数:格式标志在类ios中被定义为枚举值
- 用流成员函数put输出字符
- 标准输入流
- cin流
- 用流成员函数输入字符:无参、1个、3个参数的get,getline
- Istream类的其他成员函数
18、用typedef声明类型
19、预处理命令
typedef与预处理命令源代码:
#include<iostream> using namespace std; typedef int ARR[2]; //用typedef声明数组类型 #define MAX(x,y) x>y?x:y //带参数的宏定义 int main() { int max; ARR a; a[0]=1; a[1]=2; max=MAX(a[0],a[1]); #ifdef MAX cout<<"max="<<max<<endl; #else cout<<"MAX(x,y) is not defined! "<<endl; #endif return 0; }
20、模板
模板源代码:
#include<iostream> using namespace std; //声明类模板,Box为类模板名 template<typename TYPE> //<>中的为模板形参表 class Box//类声明 { TYPE length, width, height; public: Box(); Box(TYPE len, TYPE w, TYPE h):length(len),width(w),height(h) { } ~Box() { } TYPE volume(); }; //类外定义成员函数 template<typename TYPE> Box<TYPE>::Box() { length = 5; width = 5; height = 5; } //类外定义成员函数 template<typename TYPE> TYPE Box<TYPE>::volume() { return width * height * length; } //声明定义函数模板 template<typename TYPE> TYPE max(TYPE a,TYPE b) { return a>b?a:b; } int main() { Box<int> b1;//定义类对象 Box<int> b2(10,10,10); cout<<b1.volume()<<endl <<b2.volume()<<endl; cout<<"max="<<max(3.3,6.24)<<endl; return 0; }