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 就是下行转换
}
3、C++快速入门的更多相关文章
- Web Api 入门实战 (快速入门+工具使用+不依赖IIS)
平台之大势何人能挡? 带着你的Net飞奔吧!:http://www.cnblogs.com/dunitian/p/4822808.html 屁话我也就不多说了,什么简介的也省了,直接简单概括+demo ...
- SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)
SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...
- 前端开发小白必学技能—非关系数据库又像关系数据库的MongoDB快速入门命令(2)
今天给大家道个歉,没有及时更新MongoDB快速入门的下篇,最近有点小忙,在此向博友们致歉.下面我将简单地说一下mongdb的一些基本命令以及我们日常开发过程中的一些问题.mongodb可以为我们提供 ...
- 【第三篇】ASP.NET MVC快速入门之安全策略(MVC5+EF6)
目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...
- 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)
目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...
- Mybatis框架 的快速入门
MyBatis 简介 什么是 MyBatis? MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架.MyBatis 消除 了几乎所有的 JDBC 代码和参数的手工设置以及结果 ...
- grunt快速入门
快速入门 Grunt和 Grunt 插件是通过 npm 安装并管理的,npm是 Node.js 的包管理器. Grunt 0.4.x 必须配合Node.js >= 0.8.0版本使用.:奇数版本 ...
- 【第一篇】ASP.NET MVC快速入门之数据库操作(MVC5+EF6)
目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...
- 【第四篇】ASP.NET MVC快速入门之完整示例(MVC5+EF6)
目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...
- Vue.js 快速入门
什么是Vue.js vue是法语中视图的意思,Vue.js是一个轻巧.高性能.可组件化的MVVM库,同时拥有非常容易上手的API.作者是尤雨溪,写下这篇文章时vue.js版本为1.0.7 准备 我推荐 ...
随机推荐
- button按钮怎么实现超链接
button按钮怎么实现超链接 一.总结 1.我的按钮实现超链接是通过button内嵌a标签来实现的 <button class="am-btn am-btn-default am-b ...
- ajax的内容
ajax是什么? 通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新,可以局部刷新而不必整个页面整体刷新. url的简单认识: 进入服务器的三种方式: 1.localhost:端口号 ...
- python版 百度签到
经常玩贴吧,刚学python ,所以自己弄了一个python版的签到程序.自己的东西总是最好的. 登陆模块参考的http://www.crifan.com/emulate_login_website_ ...
- IntelliJ IDEA如何导入maven结构的web工程
第一步:打开一个现有(也可以不打开,直接用import选择Maven类型)的IntelliJ IDEA工程,点击菜单的"File"->"new"-> ...
- 20亿与20亿表关联优化方法(超级大表与超级大表join优化方法)
记得5年前遇到一个SQL.就是一个简单的两表关联.SQL跑了几乎相同一天一夜,这两个表都非常巨大.每一个表都有几十个G.数据量每一个表有20多亿,表的字段也特别多. 相信大家也知道SQL慢在哪里了,单 ...
- Google、Mozilla、Qt、LLVM 这几家的规范是明确禁用异常的
作者:陈硕链接:https://www.zhihu.com/question/22889420/answer/22975569来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出 ...
- 给指定的用户无需密码执行 sudo 的权限
给指定的用户无需密码执行 sudo 的权限 cat /etc/passwd 可以查看所有用户的列表 w 可以查看当前活跃的用户列表 cat /etc/group 查看用户组 cat /etc/pass ...
- 嵌入式 Linux应用程序如何读取(修改)芯片寄存器的值
这一问题来自项目中一个实际的需求:我需要在Linux启动之后,确认我指定的芯片寄存器是否与我在uboot的配置一致. 举个例子:寄存器地址:0x20000010负责对DDR2的时序配置,该寄存器是在u ...
- IIS特殊字符设置
简介:[iis7]请求筛选模块被配置为拒绝包含双重转义序列的请求.HTTP 错误 404.11 - Not Found 特殊字符最好替换成其他的字符,主要的特殊字符有”*”.”&”.”%”.” ...
- 洛谷P1143 进制转换
题目描述 请你编一程序实现两种不同进制之间的数据转换. 输入输出格式 输入格式: 输入数据共有三行,第一行是一个正整数,表示需要转换的数的进制n(2≤n≤16),第二行是一个n进制数,若n>10 ...