3、C++快速入门
参考书籍:
C++程序设计教程_第二版_钱能 //篇幅较少,适合快速学习
C++ Primer Plus 第六版 中文版 //篇幅较大,讲的非常详细
C++一般必须包含的头文件是#include <iostream>;导入的命名空间是:using namespace std;
1、访问控制,类和对象
class是对struct的扩展,含有数据成员和成员函数
访问控制:private、public、protected,其中private声明的数据成员仅供类内部函数使用,public声明的类外部程序也可使用,默认的属性是private
int a:int是类型,a是变量
Person per :Person是类,per是对象
(在类中通过”this->数据成员”表示类内成员)
2、程序结构
类定义(.h)/类实现(.cpp)
如果在类中仅声明了函数,在类外怎么实现?void 类名::函数名(参数){函数体}
A类实现Person类,其提供.h和.cpp,在A.h中仅声明类,其中类成员函数也仅声明,在A.cpp中通过类名::函数名来实现成员函数;B类实现main,B不关心Person类怎么实现,只需要#include <person.h>
命名空间
如果main文件中include多个h文件,同时这些h文件中存在同名的函数,其返回值和参数都一样,这个时候在h和其对于的cpp中把全部或者部分同名的函数使用namespace 名字A{}扩起来,在main中通过“名字A::同名函数名”还区分
eg:Person.h
#include <stdio.h>
namespace A{
class Person{
private:
char *name;
int age;
char *work;
public:
void setName(char *name);
int setAge(int age);
}
void printVersion(void);
}
Person.cpp
#include <stdio.h>
#include "person.h"
namespace A{
void Person::setName(char *name)
{
this->name = name;
}
int Person::setAge(int age)
{
this->age = age;
return 0;
}
void printVersion(void)
{
printf("Person111111");
}
}
Main.cpp
#include <stdio.h>
#incldue "person.h"
using A::Person;//加上这句后,main中类的声明前面可以不加“命名空间::”,这句话把A::Person放入global namespace,以后可以使用Person来表示A::Person
//using namespace A;作用同是把A空间全部导入,注意在导入多个命名空间的时候,如果命名空间中存在同名同参函数,在使用的时候还是要加上“命名空间::”来区分,但在导入的时候不会出现问题
int main(int argc,char **argv)
{
A::Person per;//声明命名空间后,类的声明需要在前面加上“命名空间::”
A::printVersion();
return 0;
}
3、重载、指针和引用
重载:函数名系统,参数不同(类型、数量、顺序)
指针和引用:引用就是别名,引用定义时必须初始化,且其只能引用变量,不能是常量:int a;int &b = a//b就是a的引用,b所指的内存和a一样;C++中经常使用引用来传递参数,引用传递的是地址,仅四字节,否则如果参数是对象,则传递对象耗费空间大
引用举例:
int add(int &b)
{
b = b +1;
return b;
}
调用add:
int a = 99;
add(a);
cout<<a<<endl;//引用会导致a变量被修改
4、构造函数
声明变量时调用构造函数
eg:Person per(变量1,变量2);//会调用构造函数void Person(变量1,变量2){};
注意:调用默认构造函数是通过Person per,而不是Person per();其会被理解为一个函数声明,返回值是Person
Person *per4 = new Person;
Person *per5 = new Person();
Person *per6 = new Person[2];
Person *per7 = new Person("list",18,"student");
Person *per8 = new Person("list",18);
如果在构造函数中通过new分配了空间,应该在析构函数中delete掉,否则只能等到主函数推出后才能被回收,析构函数在实例化对象被销毁之前的瞬间被调用,比如在一个函数中会声明这个实例化对象,在函数执行完退出时销毁该实例化对象。但是如果在函数中是通过new来实例化一个对象的时候(Person *per4 = new Person;),函数退出时per4 指针所指向的对象不会被销毁,其析构函数不会被调用,只能通过delete per4来销毁或者等整个main程序退出的时候才能回收空间,但次数不会调用对象的析构函数。
eg:PersonPerson(){
this->name = new char[10];
}
~PersonPerson(){
if(this->name)
delete this->name;
}
对象有默认的无参构造函数、无参析构函数、还有一个默认的拷贝构造函数,即在声明一个实例化对象的时候提供的参数是对象:Person per("zhangsan",18);Person per2(per);即per2使用per来初始化的,这个时候调用的构造函数就是默认的拷贝构造函数,per2的属性内容和per一样,共享地址空间,如果per内存被释放,per2在执行释放的时候也会再次释放该内存,存在风险,所以需要提供自己的拷贝构造函数:
eg:Person(Person &per)
{
this->age = per.age;
this->name = new char[strlen(per.name)+1];
strcpy(this->name,per.name);
}
函数中定义的静态实例化对象在函数退出的时候不会被销毁;再次进入函数的时候也不会被创建,其还存在
全局对象、main中局部对象、子函数中局部对象中的构造函数执行顺序:1、全局对象构造函数->main中局部对象构造函数->子函数中局部对象构造函数
如果在类A中声明了成员类对象B,则在实例化A对象的时候先调用B的构造函数,在调用A的构造函数;析构函数是先调用A的析构,在调用B的析构,即析构函数的调用顺序与构造函数的调用顺序相反(不管A中有多少个对象)
如果类中定义了有参构造函数,则系统不会在提供无参的构造函数,这时候在声明无参对象时会出错,因为已经没有无参构造函数了
5、静态成员和友员函数
static修饰的成员属于类,不属于对象,其仅有一份,通过“类名::成员名”访问,并且私有的static成员仅能被static函数访问
并且static成员必须在类外面定义和初始化:int Person::cnt =0;在类中仅是声明,在类外给其分配空间和初始化
被类设置为的友员函数可以访问本类的私有成员:eg:在类中通过“friend 函数声明”来声明该类的友员函数
6、操作符重载-通过类外函数实现
通过操作符重载可以实现通过“+”、“-”等符号实现对象的加减
class Point{
private:
int x;
int y;
friend Point operator+(Point &p1,Point &p2);
}
Point operator+(Point &p1,Point &p2)
{
Point n;
n.x = p1.x + p2.x;
n.y = p1.y + p2.y;
return n;
}
int main(int argc,char **argv)
{
Point p1(1,2);
Pont p2(2,3);
Point sum = p1+p2
}
来实现Point p(1,2);p++;++p;
注意:下面两个需要在类中声明为友员,目的是为了访问类的私有成员,如果成员是公有的,则不需要声明为friend;
Point operator++(Point &p)//++p,这里的重载函数返回了个Point对象,返回的时候会根据p值来调用构造函数Point(const Point &p)来构造一个对象,如果不使用这个返回的对象,立马会调用其析构函数并且被销毁;这样一个构造和析构过程比较浪费资源,如果把返回值声明为Point& operator++(Point &p),这样就不会新声明一个对象了,直接返回传入的p的引用,但需要注意返回引用和返回值的时候不能影响程序的执行结果
{
p.x += 1;
p.y += 1;
return p;
}
Point operator++(Point &p,int a)//p++,在main中也可以通过operator++(p,0)来调用
{
Point n;
n=p
p.x += 1;
p.y += 1;
return n;
}
使用的时候p++;++p就可以
对cout的重载,eg:Point m,n;cout<<m<<n;//cout<<m返回的就是cout,是对cout的引用这样才能cout<n;
ostream& operator<<(ostream &o,Point p)//这个函数也可以在main中通过“operator<<(cout,p)”调用
{
cout<<"("<<p.x<<","<<p.y<<")";//<<endl这里的endl表示回车
return o;
}
7、操作符重载-通过类内函数实现
把上面6中这些外部重载的函数移到类内部就可以,通过减少参数,因为类内部函数在被对象调用的时候,这个对象就是一个参数eg:p1.operator+(p2)
现在执行m = p1+p2表示为m=p1.operator+(p1);
注意:cout输出不能在类内部实现,因为"p.operator<<"其第一个参数是Point类型,而函数的第一个参数数ostream类型
重载“=”:Person& operator=(const Person& p)//如果不重载“=”,那么p = p1,会是的p里面的变量会和p1里面的变量指向同一片内存(注意:Point p=p1执行的是拷贝构造函数)
{
if(this == &p)
return *this;
this->age = p.age;
if(this->name){
delete this->name;
}
if(this->work){
delete this->work;
}
this->name = new char[strlen(per.name)+1];
strcpy(this->name,per.name);
this->work = new char[strlen(per.work)+1];
strcpy(this->work,per.work);
return *this;
}
8、访问控制和继承
class Person{};
calss Student:public Person{};//Student类继承Person类
基类成员在派生类中的访问控制属性
基类访问属性 public protected private
继承类型
public public protected 隔离
protected protected protected 隔离
private private private 隔离
(无论那种继承方式,在派生类内部使用父类时并无发别;仅影响外部代码对派生类的使用和派生类的之类)
说明:1、派生类不能访问基类的私有成员;
2、派生类可以通过protected或者public的成员函数访问私有成员;
3、派生类可以访问protected成员,其他外部代码不可以(protected成员外界不可访问);
4、派生类继承到的成员可以修改成员的权限(protected可以修改为public、private,但是private成员不能被修改权限)
(eg:在派生类中通过“public:
using 父类名::父类成员名或者函数名”可以修改成员变量和函数的权限)
Base &b2=d1;// 子类对象当父类对象
b2.print(); // 调用父类函数
9、多重继承
派生类(子类)有多个父类(基类)
eg:
class Sofa{};
class Bed{};
class Sofabed:public Sofa,public Bed{};//这里如果不写继承方法,则默认的是private继承
虚拟继承:D继承A和C,这个时候在D中怎么访问A和C同名的函数呢:1、d.A::同名函数();2、把A和C中同名的部分抽象出来一个类B,A和C通过virtual来虚继承,对于虚继承的A和C,在子类D中只会有一份B中的成员(class A:virual public B)
10、构造顺序
A、先父类(基类)后子类(派生类)
B、对于父类:先虚拟基类后一般基类
C、对于子类:先对象成员,后自己的构造函数
11、多态(使用相同的调用方法,对于不同的对象会调用不同的类里面的实现的函数,根据传入的对象类型自己识别该类型,并调用类型的函数)
eg:反面例子,都是调用父类的eating函数
class Human{
public:
void eating(void){cout<<"use hand to eat"<<endl;}
}
class Englishman:public Human{
public:
void eating(void){cout<<"use knife to eat"<<endl;}
}
class Chinese:public Human{
public:
void eating(void){cout<<"use chopsticks to eat"<<endl;}
}
void test_eating(Human & h)
{
h.eating();
}
int main(int argc,char **argv)
{
Human h;
Englishman e;
Chinese c;
test_eating(h);
test_eating(e);
test_eating(c);
}
执行结果:
use hand to eat
use hand to eat
use hand to eat
修改test_eating函数让其能够自己判断传入的参数是属于那个类,并调用该类的eating函数(通过虚函数)
class Human{
public:
virtual void eating(void){cout<<"use hand to eat"<<endl;}
}
class Englishman:public Human{
public:
virtual void eating(void){cout<<"use knife to eat"<<endl;}//这里的virtual可以不用写,因为父类是virtual,其子类覆盖后也是virtual函数
}
class Chinese:public Human{
public:
virtual void eating(void){cout<<"use chopsticks to eat"<<endl;}//这里的virtual可以不用写,因为父类是virtual,其子类覆盖后也是virtual函数
}
//void test_eating(Human * h)
void test_eating(Human & h)//使用指针或者引用来使用对象时,才有多态,如果是test_eating(Human h)则即使使用虚函数也是调用父类的eating,因为这个时候如果调用的是test_eating(e),这个e被强制转换成Human类,会把对象里面那个指向自己虚函数表的那个指针丢掉
{
h.eating();
}
int main(int argc,char **argv)
{
Human h;
Englishman e;
Chinese c;
test_eating(h);
test_eating(e);
test_eating(c);
}
执行结果:
use hand to eat
use knife to eat
use chopsticks to eat
说明:对于非虚函数,在编译时已经确定好调用的是那个类的函数;对于虚函数,在运行是才能确定
对于有虚函数的类,其对象里面有个指针,指向虚函数表(虚函数表里面也是一些地址,指向虚函数);调用虚函数的时候,是通过这个指针来调用虚函数
(通过sizeof(对象),可以发现有virtual函数和无virtual函数的对象大小不一样)(虚函数表里面出来有虚函数地址外,还有对象所属类和基类的相关信息)
静态成员函数、内联函数、构造函数都不能是虚函数,析构函数一般都声明为虚函数(举例如下)
int main(int argc,char **argv)
{
Human* h =new Human;
Englishman* e = new Englishman;
Chinese *c = new Chinese;
Human *p[3] = {h,e,c};
int i;
for(i = 0;i<3;i++)
{
p[i]->eating;
delete p[i];
}
}
如果析构函数不是虚函数:执行结果:(在上面的各个Human、Englishman、Chinese的类中添加析构函数)
use hand to eat
~Human()
use knife to eat
~Human()
use chopsticks to eat
~Human()
如果析构函数是虚函数:执行结果
use hand to eat
~Human()
use knife to eat
~Englishman()
use chopsticks to eat
~Chineseman()
重载:函数参数不同,名字相同,不可设为虚函数(当重载函数的返回值是本类的指针或者引用时可以设置为虚函数,能实现多态);
覆盖:函数名字、函数参数和返回值都相同,可设为虚函数;
12、类型转换
隐式类型转换:double d = 100.1;int i =d;//double转int,i = 100;
char *str = "100.ask";int *p = str;//char * 转为int *
(隐式类型转换有编译器来实现,其并不一定能猜测出代码的意图,编译的时候会出现warning)
显示类型转换:A、强制类型转换:double d = 100.1;int i =(int)d;
char *str = "100.ask";int *p =(int *) str;
B、动态转换:dynamic_cast<type-id>(expression):该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void*,如果tpe-id是类指针类型,那么expression也必须是一个指针;如果type-id是一个引用,那么expression也必须是一个引用;
void test_eating(Human & h)
{
Englishman *pe;
Chinese *pc;
h.eating();
if(pe = dynamic_case<Englishman *>(&h))
cout<<"This human is Englishman"<<endl;
if(pe = dynamic_case<Chinese*>(&h))//这里的&h是取址
cout<<"This human is Chinese"<<endl;
}
int main(int argc,char **argv)
{
Human h;
Englishman e;
Chinese c;
test_eating(h);
test_eating(e);
test_eating(c);
}
执行结果:
use hand to eat
use knife to eat
This human is Englishman
use chopsticks to eat
This human is Chinese
C、静态转换:static_cast<type-id>(expression)编译器在编译的时候已经决定好怎么转换
下行转换会存在隐患eg:Englishman *pe = static_case<Englishman *>(&h);编译能成功,下行转换存在风险 但如果使用Englshman类的方法是程序会崩溃
Englishman *pe = static_case<Englishman *>(&g);编译不能成功
Chinese*pe = static_case<Chinese*>(&g);编译能成功,没问题,上行转换没问题
D、使用reinterpret_cast从新解析转换(同c语言风格的强制类型转换):double d = 100.1;int i =reinterpret_cast<int>(d);
E、通过const_case去掉变量const或者volatile属性:const char *str = "100ask"; char *str2 = const_case<char *>(str);
注意:动态转换用于多态场合,即:必须有虚函数,引用转换的时候需要用到虚函数表里面的类和基类信息来转换
主要用于类层次间的上行转换(派生类转换成基类)和下行转换(基类转换成派生类),还可以用于类之间的交叉转换
在类层次间进行上行转换是,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全
eg:
class Guangximan:public Chinese{
public:
virtual void eating(void){cout<<"use chopsticks to eat,I come from guangxi"<<endl;}
}
void test_eating(Human & h)
{
//Englishman& pe = dynamic_case<Englishman&>(h);执行的时候会出错,转换失败,引用没指向一个实体,肯定出错
Chinese & pc = dynamic_case<Chinese&>(h);
Guangximan &pg = dynamic_case<Guangximan &>(h);
h.eating();
}
int main(int argc,char **argv)
{
Guangximan g;
test_eating(g);//Guangximan类转换成Human类就是上行转换,进入函数后Human类转换成Chinese和Guangximan 就是下行转换
}
最新文章
- [No00009C]Visual Studio在 解决方案资源管理器 里同步定位打开的文件
- 强连通 HDU 3639
- 用C#制作推箱子小游戏
- !!!jQuery中事件绑定 推荐使用.delegate()或者live()
- JS异步加载的三种方式
- CodeBlocks对C++模板的支持
- Java IO 节点流 ByteArrayInput/OutputStream
- Akka(21): Stream:实时操控:人为中断-KillSwitch
- [2014-02-19]如何移除响应头中的.net framework 版本信息 以及mvc版本信息?
- Java学习笔记17---方法的重载与重写
- C语言描述二叉树的实现及操作(链表实现)
- Python 库/模块的安装、查看
- 10、Typescript-类的基本用法
- springboot设置session超时和session监听
- jQuery中mouseleave和mouseout的区别详解
- Ambari集成Kerberos报错汇总
- TCP与UDP区别小结
- 学习ReentrantLock
- 【一些容易忘记的node的npm命令】【收集】
- Python: tree data structure