C++ note
主要是为了学习c++的类和对象
内容摘自 c++概述
http://see.xidian.edu.cn/cpp/biancheng/cpp/rumen_1/
1,变量
,C++中,我们可以在函数体内声明一个静态局部变量(Static Local Variable)。它在函数运行结束后不会消失,并且只有声明它的函数中能够使用它。我们可以在函数体外声明一个变量,它称为全局变量(global variable),在某一层次声明的变量的作用域就终止于该变量所在层次的末尾。在某个函数的同一语法层次内不能声明多个名字相同的变量。在函数体内声明一个静态局部变量(Static Local Variable)。它在函数运行结束后不会消失,并且只有声明它的函数中能够使用它(static int a;)。
2,逻辑
if (m!=0 && n/m<1),当m=0时,电脑不会去尝试用n/m了,而是直接跳过整句语句。这样,我们就能够避免除数为零的错误了
“……?……:……”称为条件操作符,它的运算优先级比逻辑或还低,是目前为止优先级最低的操作符, max=(a>=b)?a:b;
在switch语句中,我们要记住四个关键词,分别是switch、case、default和break。
switch(表达式)
{
case 常量表达式1:
{
语句块1;
break;
}
default:
{
语句块n+1;
}
}
cout <<setw(2) <<a <<b;语句中域宽设置仅对a有效,对b无效。
3,函数
一旦函数运行结束,那么该函数中声明的参数和变量都将消失。
return 符合返回值类型的表达式;
在返回空类型的函数中可以使用return语句,人为地停止函数的运行,也可以不使用return语句,使其运行完所有语句后自然停止。
4,存储
在定义默认参数时,必须在函数声明中定义
void create(int n=100);//在函数声明中定义默认参数
int size=sizeof(array)/sizeof(int);//数组的大小
数组作为参数传递给函数的只是数组首元素的地址,函数在需要用到后面元素时再按照这个地址和数组下标去查找
指针和整数C的加减法是指针向前或向后移动C个对应类型的存储区域
数组名是指针,但它是一个指针常量。也就是说,不带下标的数组名不能作为左值。
数组的大小在编译前必须是已知的常量表达式
不同于数组,结构是按值传递的。也就是说整个结构的内容都复制给了形参,即使某些成员数据是一个数组。使用返回结构体方式实现修改
5,调试
用函数声明和定义分离的方式:把所有的声明都放在shape.h中,把所有的定义放在shape.cpp中
#开头的命令都是编译预处理命令,比如#if、#else、#endif、#ifdef、#ifndef、#undef和#define
无论这个文件是C++提供的还是自己编写的,使用#include "文件名"命令一定是正确的
如果包含头文件时写作如#include <iostream>,但是没有using namespace std;必须要使用std名字空间。
在编译阶段发生的错误称为编译错误(Compile Error),在运行阶段发生的错误称为运行时错误(Runtime Error)。对于编译错误,我们通过检查并修正语法错误来解决;对于运行时错误,我们通过检查并修正语意(程序设计思想)错误来解决
6,对象
面向对象(Object Oriented,简称OO)
对象就是任何我们可以想象出来的具体的物体
能够抽象地描述某一些具有共性的物体的词称为类(Class)
类与结构相似,它也是一种由用户自己定义的数据类型;它也可以通过成员数据来刻画一些现实生活中的东西。不同的是,对它的操作并不是通过普通的函数,而是通过类的成员函数来实现的
定义完一个类之后务必要在最后加上一个分号。
类中变量在定义或声明时不说明该成员数据(或成员函数)是公有的还是私有的,则默认为私有的。
class Node//定义一个链表结点类
{
public:
int idata;//数据能够被外部访问
char cdata;//数据能够被外部访问
private:
Node *prior;//前驱结点的存储位置保密
Node *next;//后继结点的存储位置保密
};
如果一个类的某个成员函数是私有的,那么它只能被这个类的其他成员函数调用。
const这个保留字来保护成员数据不被成员函数改变
成员函数的写法就是在函数的参数表后面加上一个const,比如:int readi() const;//通过该函数读取idata,但不能改变任何成员数据
"::"操作符,它表示该函数是属于某一个类的,称为域解析操作符
定义类成员函数
//node.h
class Node//定义一个链表结点类
{
public:
int readi() const;//通过该函数读取idata,但不能改变任何成员数据
bool set(int i);//重载,通过该函数修改idata
bool set(char c);// 重载,通过该函数修改cdata
private:
int idata;//存储数据保密
char cdata;//存储数据保密
};//类定义结束,分号切勿忘记
int Node::readi() const //成员函数readi的定义
{
return idata;
}
bool Node::set(int i)//重载成员函数定义
{
idata=i;
return true;
}
bool Node::set(char c)
{
cdata=c;
return true;
}
调用成员函数
//main.cpp
#include <iostream>
#include "node.h"//包含我们编写好的链表结点类头文件,必须用双引号
using namespace std;
int main()
{
Node a;//创建一个链表结点对象a
a.set(1);//设置idata
a.set('A');//设置cdata
cout <<a.readi() <<endl;
cout <<a.readc() <<endl;
return 0;
}
对象的引用
Node b;//声明一个结点对象
Node &a=b;//声明一个引用
a.set(0);//效果与b.set(0)相同
对象指针
Node b;//声明一个结点对象
Node *a=&b;//声明一个对象指针
a->set(0);//效果与b.set(0)相同
a->readi();//效果与b.readi()相同
对象的初始化的方法,增加初始化函数 void init(type,type);
构造函数是一种随着对象创建而自动被调用的函数,与类同名的成员函数就是构造函数,它的主要用途是为对象作初始化,它是一个公有的成员函数,并且构造函数没有返回值类型。
class Node//定义一个链表结点类
{
public:
Node();//构造函数的声明,构造函数是公有的成员函数,没有返回值类型
private:
int idata;//存储数据保密
char cdata;//存储数据保密
};
Node::Node() //构造函数的定义
{
cout <<"Node constructor is running..." <<endl;//提示构造函数运行
idata=0;//初始化idata
cdata='0';//初始化cdata
}
带参数的构造函数(构造函数也可以重载)
Node::Node(int i,char c)//构造函数重载1,默认参数只需要在函数原型中出现
{
cout <<"Node constructor is running..." <<endl;
idata=i;
cdata=c;
prior=NULL;
next=NULL;
}
对应于main中的,Node b(8);//创建一个链表结点对象b,调用构造函数重载1,参数c默认为'0'
在C++中,每个类都有且必须有构造函数。如果用户没有自行编写构造函数,则C++自动提供一个无参数的构造函数,称为默认构造函数。这个默认构造函数不做任何初始化工作。一旦用户编写了构造函数,则这个无参数的默认构造函数就消失了。如果用户还希望能有一个无参数的构造函数,必须自行编写
深拷贝构造函数是真正意义上的复制了链表a,并且使得链表a和链表b各自独立,互不干扰。这才是自定义拷贝构造函数存在的重要意义
//拷贝构造
CExample(const CExample& C)
{
a = C.a;
cout<<"copy"<<endl;
}
CExample B = A; // CExample B(A); 也是一样的,这里不是赋值
析构函数(Destructor)能随着对象的消亡而自动被调用
对象作为函数参数时,会先调用对象的拷贝构造函数,把参数的值复制给临时对象,函数结束后,临时对象调用析构函数
构造函数用于初始化对象,拷贝构造函数用于访问创建的对象,用于类向对象中的似有成员传递指
对象rect1的p和rect2的指针成员p各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”
初始式example
#include "iostream"
#include "stdlib.h"
using namespace std;
class A
{
public:
A(int i,char c);
void show();
private:
int idata;
char cdata;
};
A::A(int i,char c)
{
idata=i;
cdata=c;
}
void A::show()
{
cout <<"A.show"<<endl<<idata<<endl;
cout<<cdata<<endl;
cout<<"A.show end"<<endl;
}
class B
{
public:
B(int i,char c);
void show();
private:
int bint;
A ao;
A *p;
char bchar;
};
B::B(int i,char c):ao(i,c)
{
cout<<"B copy and creat"<<endl;
cout<<i<<c<<endl;
p=&ao;
cout<<"B copy and creat end"<<endl;
}
void B::show()
{
cout<<"B.show begin"<<endl;
p->show();//或者ao.show();
cout<<"B.show end"<<endl;
}
int main()
{
A a(1,'a');
a.show();
B b(2,'b');
b.show();
system("pause");
}
如果指针指向不是用 new 分配的内存地址,则在该指针上使用 delete 是不合法的。
const int *pci = new const int(1024);
动态创建的 const 对象必须在创建时初始化,并且一经初始化,其值就不能再修改。
由new生成的ptemp,在执行delete之后,还可以对其进行赋值操作,猜想delete 操作删除的是指针指向内存的内容
一个类成员中有其他类对象,可以用以下方式构造(linklist成员中有node类,pucurrent和head分别为node类的对象和对象指针
方式一
Linklist::Linklist(int i,char c):head(i,c)//类名::构造函数名(参数表):成员对象名1(参数表),链表类构造函数,调用head对象的构造函数重载1,详见Node.h文件
{
cout<<"Linklist constructor is running..."<<endl;
pcurrent=&head;
}
方式二,需要在node类中声明linklist是友元类,friend class Linklist;
Linklist::Linklist(Linklist &l):head(l.head)
{
cout<<"Linklist Deep cloner running..." <<endl;
pcurrent=&head;
Node * ptemp1=l.head.next;//直接访问私有成员数据
while(ptemp1!=NULL)//复制链表
{
Node * ptemp2=new Node(ptemp1->idata,ptemp1->cdata,pcurrent,NULL);
pcurrent->next=ptemp2;
pcurrent=pcurrent->next;
ptemp1=ptemp1->next;
}
一个函数要访问一个或多个对象的私有成员时,我们可以用友元来解决这个问题。
friend void ShowNode(Node &n);//声明友元函数ShowNode,在节点类中声明
在main函数中可以使用ShowNode(b);//用友元函数输出b结点的内容
操作符重载,a是复数
void Complex::operator =(Complex a)
{
real=a.real;
img=a.img;
}
Complex Complex::operator +(Complex a)
{
Complex temp(a.real+real,a.img+img);
return temp;
}
这样在main函数中,可以进行操作
Complex a(3,2),b(5,4),c(1,1),d(4,2),temp;
temp=a+b;
继承的方法,使我们可以用一种已经编写好的类来扩写成一个新的类
class 子类名:[public,private,protect]父类名;
private(私有)和protected(保护)都能实现类的封装性。private能够对外部和子类保密,即除了成员所在的类本身可以访问之外,别的都不能直接访问。protected能够对外部保密,但允许子类直接访问这些成员。
private是私有继承父类所有的公有、保护成员继承到子类时,类型会发生改变。父类的公有成员在子类中变成了私有成员,父类的保护成员在子类中也变成了私有成员。父类不能在访问子类中成员
class Stack:private Linklist//私有继承链表类
void Stack::show()
{
Show();//用Linklist类的成员函数实现功能
}
父类的成员对象是最先构造的,接着是运行父类的构造函数,最后运行子类的构造函数。
子类的构造函数的参数传递给父类的构造函数
Stack::Stack(int i,char c):Linklist(i,c)//将子类构造函数的参数传递给父类的构造函数
{
cout <<"Stack constructor with parameter is running..." <<endl;
}
使用了继承之后,析构函数的运行顺序依然恰好与构造函数的运行顺序相反。
父类指针能否指向子类对象?子类指针能否指向父类对象?
在公有继承情况下父类的对象指针指向子类对象是允许的。子类的对象指针指向父类是禁止的
用父类的对象指针指向子类对象,那么这个指针无法使用子类中扩展出的成员。
子父类例子
如果有一个本科生对象s1和一个学生对象s2,那么显然s1.study()会是学习高等数学和大学英语,s2.study()会是随便学些什么。但是,如果有一个学生类的指针sp,它也能指向本科生对象,这时调用sp->study()会是怎么样的呢?我们发现,即使它指向一个本科生对象,它也只能“随便学些什么”。
不同的子类的同名成员函数有着不同的表现形式,称为多态性
多态性往往只有在使用对象指针或对象引用时才体现出来。多态性是面向对象的一个标志性特点,没有这个特点,就无法称为面向对象。
设置虚函数的方法为:在成员函数的声明最前面加上保留字virtual。注意,不能把virtual加到成员函数的定义之前,否则会导致编译错误。
virtual void study();//把学习设置为虚函数,父类中
void student::study()//成员函数定义处没有virtual
{
cout <<"随便学些什么。" <<endl;
return;
}
virtual void study();//把学习设置为虚函数,父类的一个子类中class Undergraduate:public student
void Undergraduate::study()//成员函数定义处没有virtual
{
cout <<"学习高等数学和大学英语。" <<endl;
return;
}
virtual void study();//把学习设置为虚函数,父类的一个子类中class Pupil:public student
void Pupil::study()
{
cout <<"学习语数外。" <<endl;
return;
}
main函数中
Undergraduate s1;
student s2;
Pupil s3;
student *sp
无论父类对象指针sp指向哪种子类对象,sp->study()的执行结果总是与对应的类相符合的
要使用虚函数实现多态性,至少要使各个函数的参数格式也完全相同。
当子类函数和父类参数不同时,会执行父类中的同名成员函数
编写成员函数的时候,可以把尽可能多的成员函数设置为虚函数。
给析构函数的前面加上保留字virtual, virtual ~Animal();//虚析构函数,父类animal中
子类中
Cat::~Cat()
{
cout <<"Cat destructor is running..." <<endl;
}
main中
Animal *pa=new Cat(2,1);
Cat *pc=new Cat(2,4);
delete pa;
delete pc;
结果:
Cat constructor is running...
Animal consturctor is running...
Cat constructor is running...
Delete pa:
Cat destructor is running...
Animal destructor is running...
Delete pc:
Cat destructor is running...
Animal destructor is running...
子类和父类的析构函数都被执行
虚函数是为了实现多态,而虚析构函数是为了同时运行父类和子类的析构函数,使资源得以释放。
只能用于被继承而不能直接创建对象的类设置为抽象类(Abstract Class)。
类中的确存在,但是在父类中无法确定具体实现的成员函数称为纯虚函数。纯虚函数是一种特殊的虚函数,它只有声明,没有具体的定义。抽象类中至少存在一个纯虚函数;存在纯虚函数的类一定是抽象类。
设置纯虚函数之后并不影响多态的实现,但是却将父类变成了抽象类,限制了父类对象的创建,virtual void study()=0;//在父类的声明中通过代码实现,声明study为纯虚函数
完