跟我一起学C++.docx
《跟我一起学C++.docx》由会员分享,可在线阅读,更多相关《跟我一起学C++.docx(98页珍藏版)》请在冰豆网上搜索。
跟我一起学C++
《跟我一起学C++》
1C++介绍
1.1为什么要学习C++
编程语言的需求总结为四个:
效率(efficiency),灵活(flexibility),抽象(abstraction),生产力(productivity)。
C语言注重前两者,C++注重前三者,JAVA、.net这些注重后两者。
1.2C++为什么难学
1.3C++11值得学习的新特性
1.4几本推荐的书籍
1.5开发工具选择
2从C到C++
(一)
2.1bool类型
逻辑型也称布尔型,其取值为true(逻辑真)和false(逻辑假),存储字节数在不同编译系统中可能有所不同,VC++中为1个字节。
2.2const限定符
const只读
用const给字面常量起个名字(标识符),这个标识符就称为标识符常量;因为标识符常量的声明和使用形式很像变量,所以也称常变量
定义的一般形式:
const数据类型常量名=常量值;
数据类型const常量名=常量值;
注意事项:
常变量在定义时必须初始化;
常变量初始化之后,不允许再被赋值;
2.3const与#define
const定义的常量与#define定义的符号常量的区别
const定义的常量有类型,而#define定义的没有类型,编译可以对前者进行类型安全检查,而后者仅仅只是做简单替换
const定义的常量在编译时分配内存,而#define定义的常量是在预编译时进行替换,不分配内存。
作用域不同,const定义的常变量的作用域为该变量的作用域范围。
而#define定义的常量作用域为它的定义点到程序结束,当然也可以在某个地方用#undef取消
定义常量还可以用enum,尽量用const、enum替换#define定义常量。
2.4结构体内存对齐
什么是内存对齐
编译器为每个“数据单元”按排在某个合适的位置上。
C、C++语言非常灵活,它允许你干涉“内存对齐”
为什么要对齐
性能原因:
在对齐的地址上访问数据快。
如何对齐
第一个数据成员放在offset为0的位置
其它成员对齐至min(sizeof(member),#pragmapack所指定的值)的整数倍。
整个结构体也要对齐,结构体总大小对齐至各个成员中最大对齐数的整数倍。
3从C到C++
(二)
3.1域运算符
C++中增加的作用域标识符:
:
用于对与局部变量同名的全局变量进行访问
用于表示类的成员,这将在关于类的一节中详细说明
3.2new、delete运算符
new运算符可以用于创建堆空间
成功返回首地址
语法:
指针变量=new数据类型;
指针变量=new数据类型[长度n];
delete运算符可以用于释放堆空间
语法:
delete指针变量;
delete[]指针变量;
3.3重载
相同的作用域,如果两个函数名称相同,而参数不同,我们把它们称为重载overload
函数重载又称为函数的多态性
函数重载不同形式:
形参数量不同
形参类型不同
形参的顺序不同
形参数量和形参类型都不同
调用重载函数时,编译器通过检查实际参数的个数、类型和顺序来确定相应的被调用函数
3.4namemanagling与extern"C"
namemanagling这里把它翻译为名字改编。
C++为了支持重载,需要进行namemanagling
extern“C”实现C与C++混合编程
#ifdef__cpluscplus
extern“C”
{
#endif
...
#ifdef__cpluscplus
}
#endif
3.5带默认参数的函数
函数声明或者定义的时候,可以给形参赋一些默认值
调用函数时,若没有给出实参,则按指定的默认值进行工作
函数没有声明时,在函数定义中指定形参的默认值
函数既有定义又有声明时,声明时指定后,定义后就不能再指定默认值
默认值的定义必须遵守从右到左的顺序,如果某个形参没有默认值,则它左边的参数就不能有默认值。
voidfunc1(inta,doubleb=4.5,intc=3);//合法
voidfunc1(inta=1,doubleb,intc=3);//不合法
函数调用时,实参与形参按从左到右的顺序进行匹配
3.6带默认形参值的函数的二义性
重载的函数中如果形参带有默认值时,可能产生二义性
4从C到C++(三)
4.1引用
引用是给一个变量起别名
定义引用的一般格式:
类型 &引用名=变量名;
例如:
inta=1;
int&b=a;
//b是a的别名,因此a和b是同一个单元
注意:
定义引用时一定要初始化,指明该引用变量是谁的别名
在实际应用中,引用一般用作参数传递与返回值
4.2const引用
const引用是指向const对象的引用
4.3引用传递
引用传递方式是在函数定义时在形参前面加上引用运算符"&"
例如:
s&a,int&b);
注意:
引用作参数时,函数的实参与形参在内存中共用存储单元,因此形参的变化会使实参同时变化。
4.4引用作为函数返回值
引用的另一个作用是用于返回引用的函数
函数返回引用的一个主要目的是可以将函数放在赋值运算符的左边。
注意:
不能返回对局部变量的引用。
4.5引用与指针区别
引用访问一个变量是直接访问,而指针是间接访问。
引用是一个变量的别名,本身不单独分配自己的内存空间,而指针有自己的内存空间。
引用一经初始化不能再引用其它变量,而指针可以。
5从C到C++(四)
5.1内联函数
当程序执行函数调用时,如果使用频率却很高,程序频繁调用该函数所花费的时间却很多。
为了提高效率。
为了协调好效率和可读性之间的矛盾,C++提供了另一种方法,即定义内联函数,方法是在定义函数时用修饰词inline。
例:
(在函数前,加inline)
inlineintmax(inta,intb)
{
returna>b?
a:
b;
}
相当于
#defineMAX(a,b)(a)>(b)?
(a):
(b)
5.2内联函数与带参数宏区别
内联函数调用时,要求实参和形参的类型一致,另外内联函数会先对实参表达式进行求值,然后传递给形参;而宏调用时只用实参简单地替换形参。
内联函数是在编译的时候、在调用的地方将代码展开的,而宏则是在预处理时进行替换的
在C++中建议采用inline函数来替换带参数的宏。
5.3新的类型转换运算符
旧式转型
(T)expr
T(expr)
新式转型
const_cast(expr)
static_cast(expr)
reinterpret_cast(expr)
dynamic_cast(expr)
5.4static_cast(expr)
编译器隐式执行的任何类型转换都可以由static_cast完成
当一个较大的算术类型赋值给较小的类型时,可以用static_cast进行强制转换。
可以将void*指针转换为某一类型的指针
可以将基类指针指向派生类指针
无法将const转化为nonconst,这个只有const_cast才可以办得到
5.5const_cast(expr)
用来移除对象的常量性(castawaytheconstness)
const_cast一般用于指针或者引用
使用const_cast去除const限定的目的不是为了修改它的内容
使用const_cast去除const限定,通常是为了函数能够接受这个实际参数
5.6reinterpret_cast(expr)
reinterpret_cast“通常为操作数的位模式提供较低层的重新解释”也就是说将数据以二进制存在形式的重新解释。
inti;
char*p="Thisisaexample.";
i=reinterpret_cast(p);
//此时结果,i与p的值是完全相同的。
int*ip
char*pc=reinterpret_cast(ip);
//程序员需要记得pc所指向的真实对象是int型,并非字符串。
//如果将pc当作字符指针进行操作,可能会造成运行时错误
//如intlen=strlen(pc);
5.7dynamic_cast(expr)
执行“安全向下”转型操作,也就是说支持运行时识别指针或所指向的对象,这是唯一个无法用旧式语来进行的转型操作。
6面向对象介绍
(一)
6.1程序
机器语言与汇编语言
机器语言就是一些机器能读懂的二进制指令集合
汇编语言将机器指令映射为一些可以被人读懂的助记符,如ADD、SUB等。
高级语言
高级语言屏蔽了机器的细节,提高了语言的抽象层次,程序中可以采用具有一定含义的数据命名和容易理解的执行语句。
这使得在书写程序时可以联系到程序所描述的具体事物。
6.2结构化程序设计
面向过程
“程序=算法+数据结构”
特点是数据与程序分离,即数据与数据处理分离。
结构化程序设计
基本思想是采用自顶向下、逐步细化的设计方法和单入单出的控制结构。
其理念是将大型程序分解成小型、便于管理的任务。
如果其中的一项任务仍然过大,则将它分解为更小的任务。
这一过程将一直持续下去,直到将程序划分为小型的,易于编写的模块。
结构化程序设计缺陷:
程序难以管理
数据修改存在问题
程序可重用性差
用户要求难以在系统分析阶段准确定义,致使系统在交付使用时产生许多问题。
用系统开发每个阶段的成果来进行控制,不能适应事物变化的要求。
面向过程程序设计缺点的根源在于数据与数据处理分离
6.3面向对象程序设计
面向将系统看成通过交互作用来完成特定功能的对象的集合。
每个对象用自己的方法来管理数据。
也就是说只有对象内部的代码能够操作对象内部的数据。
7面向对象介绍
(二)
7.1面向对象程序设计
面向对象(ObjectOriented)是认识事务的一种方法,是一种以对象为中心的思维方式
面向对象的程序设计:
对象=(算法+数据结构)
程序=对象+对象+……+对象
面向对象程序设计模拟自然界认识和处理事物的方法,将数据和对数据的操作方法放在一起,形成一个相对独立的整体——对象(object),同类对象还可抽象出共性,形成类(class)。
一个类中的数据通常只能通过本类提供的方法进行处理,这些方法成为该类与外部的接口。
对象之间通过消息(message)进行通讯。
7.2数据抽象
结构化设计方法应用的是过程抽象。
所谓过程抽象是将问题域中具有明确功能定义的操作抽取出来,并将其作为一个实体看待。
数据抽象是较过程抽象更高级别的抽象方式,将描述客体的属性和行为绑定在一起,实现统一的抽象,从而达到对现实世界客体的真正模拟。
7.3实体、对象、类之间的关系
现实世界中的实体可以抽象出类别的概念。
对应于计算机世界就有一个类(class)的概念,因为类是一个抽象的概念的对应体,所以计算机不给它分配内存,只给对象分配内存。
左图表达了计算机世界与现实世界之间的对应关系。
7.4从机算机的观点看对象
对象是计算机内存中的一块区域。
通过内存分块每个对象在功能上相对保持独立。
这些内存不但存储数据,也存储代码。
这保证对象是受保护的,只有对象中的代码能访问存储于对象中的数据。
这清楚地限定了对象所具有的功能,并且使得对象不受未知外部事件的影响,从而使自己的数据和功能不会因此遭受破坏。
对象之间只能通过函数调用也就是发送消息来实现相互通信。
当对象的一个函数被调用时,对象执行内部的代码来响应该调用,从而使对象呈现一定的行为。
这个行为及其呈现出来的结果就是该对象所具有的功能。
7.5面向对象的基本特征
抽象
封装
继承
多态
7.6抽象
抽象是从具体到一般的过程
抽象是人们认识事物的一种方法
抓住事物本质,而不是内部具体细节或具体实现
7.7封装
封装是指按照信息屏蔽的原则,把对象的属性和操作结合在一起,构成一个独立的对象。
通过限制对属性和操作的访问权限,可以将属性“隐藏”在对象内部,对外提供一定的接口,在对象之外只能通过接口对对象进行操作。
封装性增加了对象的独立性,从而保证了数据的可靠性。
外部对象不能直接操作对象的属性,只能使用对象提供的服务。
7.8继承
继承表达了对象的一般与特殊的关系。
特殊类的对象具有一般类的全部属性和服务。
当定义了一个类后,又需定义一个新类,这个新类与原来的类相比,只是增加或修改了部分属性和操作,这时可以用原来的类派生出新类,新类中只需描述自己所特有的属性和操作。
继承性大大简化了对问题的描述,大大提高了程序的可重用性,从而提高了程序设计、修改、扩充的效率。
继承通俗讲就是拿来主义。
7.9多态
多态(Polymorphism)按字面的意思就是“多种状态”。
在面向对象语言中,接口的多种不同的实现方式即为多态。
引用CharlieCalverts对多态的描述——多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自“Delphi4 编程技术内幕”)。
简单的说,就是一句话:
允许将子类类型的指针赋值给父类类型的指针。
多态性在ObjectPascal和C++中都是通过虚函数(VirtualFunction)实现的。
详见XX百科多态。
多态性:
同一个消息被不同对象接收时,产生不同结果,即实现同一接口,不同方法。
一般类中定义的属性和服务,在特殊类中不改变其名字,但通过各自不同的实现后,可以具有不同的数据类型或具有不同的行为。
当向图形对象发送消息进行绘图服务请求后,图形对象会自动判断自己的所属类然后执行相应的绘图服务。
多态使得我们能够以一致的观点来看待不同但大相径庭的对象。
7.10继承与多态
继承和多态性组合,可以生成很多相似但又独一无二的对象。
继承性使得这些对象可以共享许多相似特性,而多态又使同一个操作对不同对象产生不同表现形式。
这样不仅提高了程序设计的灵活性,而且减轻了分别设计的负担。
7.11面向对象编程方法的特性
程序设计的重点在数据而不是函数
程序由对象组成,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
对象之间通过相互协作来完成功能
函数与相关的数据紧密结合
数据可以被隐藏
很容易扩充新的数据和函数
7.12面向对象编程的优缺点
面向对象编程的优点:
易维护:
可读性高,即使改变需求,由于继承的存在,维护也只是在局部模块,维护起来是非常方便和较低成本的。
质量高:
可重用现有的,在以前的项目的领域中已被测试过的类使系统满足业务需求并具有较高的质量。
效率高:
在软件开发时,根据设计的需要对现实世界的事物进行抽象,产生类。
这样的方法解决问题,接近于日常生活和自然的思考方式,势必提高软件开发的效率和质量。
易扩展:
由于继承、封装、多态的特性,自然设计出高内聚、低耦合的系统结构,使得系统更灵活、更容易扩展,而且成本较低。
面向对象编程的缺点:
运行效率会下降
8类与对象
(一)
8.1类声明
//类是一种用户自定义类型,声明形式:
class类名称
{
public:
公有成员(外部接口)
private:
私有成员
protected:
保护成员
};
8.2公有、私有、保护成员
在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。
在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。
在关键字protected后面声明,与private类似,其差别表现在继承与派生时对派生类的影响不同
//02.h
#ifndef_02_H_
#define_02_H_
classClock
{
public:
//成员函数,行为
voidDisplay();
voidInit(inthour,intminute,intsecond);
public:
//数据成员,属性//可以定义为private,protected,public
inthour_;
intminute_;
intsecond_;
};
#endif//_02_H_
//02.cpp
#include
#include"02.h"
usingnamespacestd;
voidClock:
:
Display()
{
cout<"<"<"<}
voidClock:
:
Init(inthour,intminute,intsecond)
{
hour_=hour;
minute_=minute;
second_=second;
}
//main函数
#include"02.h"
intmain(void)
{
Clockc;
c.Init(11,22,33);
c.Display();
c.second_++;//类成员定义为私有时,不能访问
c.Display();
return0;
}
8.3数据抽象和封装
数据抽象是一种依赖于接口和实现分离的编程(和设计)技术。
类设计者必须关心类是如何实现的,但使用该类的程序员不必了解这些细节。
使用者只要抽象地考虑该类型做什么,而不必具体地考虑该类如何工作。
封装是一项将低层次的元素组合起来形成新的、高层次的实体的技术。
函数是封装的一种形式:
函数所执行的细节行为被封装在函数这个更大的实体中。
被封装的元素隐藏了它们的实现细节——可以调用函数,但是不能直接访问函数所执行的语句。
同样地,类也是一个封装的实体:
它代表若干成员的聚集,设计良好的类隐藏了类实现的细节。
9类与对象
(二)
9.1内联成员函数
9.2成员函数的重载及其缺省参数
//02.h
#ifndef_02_H_
#define_02_H_
classtest
{
public:
//voidInit();
//voidInit(intx);
//voidInit(intx,inty);
//voidInit(intx,inty,intz);
voidInit(intx=0,inty=0,intz=0);
voidDisplay();
private:
intx_;
inty_;
intz_;
};
#endif//_02_H_
//02.cpp
#include
#include"02.h"
usingnamespacestd;
//voidtest:
:
Init()
//{
//x_=0;
//y_=0;
//z_=0;
//}
//voidtest:
:
Init(intx)
//{
//x_=x;
//y_=0;
//z_=0;
//}
//voidtest:
:
Init(intx,inty)
//{
//x_=x;
//y_=y;
//z_=0;
//}
//voidtest:
:
Init(intx,inty,intz)
//{
//x_=x;
//y_=y;
//z_=z;
//}
voidtest:
:
Init(intx/*=1*/,inty/*=2*/,intz/*=3*/)
{
x_=x;
y_=y;
z_=z;
}
voidtest:
:
Display()
{
cout<<"x_="<}//main函数
#include"02.h"
intmain(void)
{
testt;
t.Init();
t.Display();
t.Init
(1);
t.Display();
t.Init(2,3);
t.Display();
t.Init(4,5,6);
t.Display();
return0;
}
9.3类与结构体
class与struct的区别:
在未指定访问权限时,class默认的是私有的,struct默认是公有的
9.4隐含的this指针
成员函数有一个隐含的附加形参,即指向该对象的指针,这个隐含的形参叫做this指针
使用this指针保证了每个对象可以拥有不同的数据成员,但处理这些成员的代码可以被所有对象共享
10类与对象(三)
10.1类作用域
每个类都定义了自己的作用域称为类作用域
类作用域中说明的标识符只在类中可见。
10.2前向声明
C++中类必须先定义,才能够实例化。
两个类需要相互引用形成一个“环形”引用时,无法先定义使用。
这时候需要用到前向声明
前向声明的类不能实例化(但可以用指针或引用)。
10.3嵌套类
外围类需要使用嵌套类对象作为底层实现,并且该嵌套类只用于外围类的实现,且同时可以对用户隐藏该底层实现。
classa
{
classb
{
public:
voidfunc()
{
cout<<"inner_b"<}
};
public:
bb_;
voidfunc()
{
b_.func();
cout<<"outer_a"<}
};
intmain(void)
{
aa_;
a_.func();
a_.b_.func();
}
从作用域的角度看,嵌套类被隐藏在外围类之中,该类名只能在外围类中使用。
如果在外围类之外的作用域使用该类名时,需要加名字限定。
嵌套类中的成员函数可以在它的类体外定义。
嵌套类的成员函数对外围类的成员没有访问权,反之亦然。
嵌套类仅仅只是语法上的嵌入
10.4局部类
类也可以定义在函数体内,这样的类被称为局部类(loaclclass)。
局部类只在定义它的局部域内可见。
局部类的成员函数必须被定义在类体中。
局部类中不能有静态成员
11构造函数与析构函数
(一)
11.1构造函数.
构造函数是特殊的成员函数
创建类类型的新对象,系统自动会调用构造函数
构造函数是为了保证对象的每个数据成员都被正确初始化
函数名和类名完全相同
不能定义构造函数的类型(返回类型),也不能使用void
通常情况下构造函数应声明为公有函数,否则它不能像其他成员函数那样被显式地调用
构造函数被声明为私有有特殊的用途。
构造函数可以有任意类型和任意个数的参数,一个类可以有多个构造函数(重载)
11.2默认构造函数
不带参数的构造函数
如果程序中未声明,则系统自动产生出一个默认构造函数
11.3重载构造函数
11.4构造函数与new
11.5全局对象的构造先于main函数
11.6析构造函数
函数名和类名相似(前面多了一个字符“~”)
没有返回类型
没有参数
析构函数不能被重载