目录
1.3 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中
一、面向对象程序设计
面向对象程序设计(
Object-Oriented Programming
,OOP
)是一种新的程序设计范型。程序设计范型是指设计程序的规范、模型和风格,它是一类程序设计语言的基础。面向过程程序设计范型是使用较广泛的面向过程性语言,其主要特征是:程序由过程定义和过程调用组成(简单地说,过程就是程序执行某项操作的一段代码,函数就是最常用的过程)。
面向对象程序的基本元素是对象,面向对象程序的主要结构特点是:第一,程序一般由类的定义和类的使用两部分组成;第二,程序中的一切操作都是通过向对象发送消息来实现的,对象接收到消息后,启动有关方法完成相应的操作。
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
1.概述
1.1 对象
描述其属性的数据 以及 对这些数据施加的一组操作 封装在一起统一体
对象可以认为是 数据+操作
1.2 类
类是具有相同的数据和相同的操作的一组对象的集合(对象的集合)
1.3 消息传递
对象之间的交互
1.4 方法
对象实现的行为称为方法
2. 基本特征
2.1 抽象
2.2 封装
把数据和显示操作的代码集中起来放在对象内部,并尽可能隐蔽对象的内部细节,信息屏蔽。
2.3 继承
2.4 多态
二、命名空间
在C/C++里,变量、函数和类都是大量存在的
这些变量、函数和类的名称都将作用于全局作用域中,可能会导致命名冲突
使用命名空间的目的就在于对标识符和名称进行本地化,以避免命名冲突或名字污染
namespace关键字的出现就是针对此问题
1.命名空间的定义
使用namespace关键字,后面跟命名空间的名字 接一对{ }即可。
{ }内即为命名
注:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中
1.1 命名空间的普通定义
namespace hei{
int printf=10;
int rand=6;
int Add(int a,int b){
return a+b;
}
}
注:可以定义变量,也可以定义函数
1.2 命名空间可以嵌套
namespace hei{
int printf=10;
int rand=6;
int Add(int a,int b){
return a+b;
}
namespace hei2{ //命名空间嵌套
int a=0;
}
}
1.3 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中
2.命名空间的使用
2.1 加命名空间名称及作用域限定符
符号 “::” 在C++中叫做作用域限定符
通过 “命名空间名称::命名空间成员” 可以访问到命名空间中相应的成员
namespace hei
{
int printf = 1;
int rand = 2;
int Add(int a, int b)
{
return a + b;
}
}
#include<iostream>
int main()
{
printf("%d\n",hei::printf);
}
输出 : 1
2.2 使用using namespace命名空间名称引入
namespace hei
{
int rand = 2;
int Add(int a, int b)
{
return a + b;
}
}
#include<iostream>
using namespace hei;
int main()
{
printf("%d\n",hei::rand);
}
输出:2
但是这种方法存在弊端
譬如我们在命名空间中定义了一个名字叫printf的变量,没那么之后再将namespace hei这个命名空间引入的时候,就会造成命名污染
namespace hei
{
int printf=10;
int rand = 2;
int Add(int a, int b)
{
return a + b;
}
}
#include<iostream>
using namespace hei;
int main()
{
printf("%d\n",rand); //这里的printf存在问题
}
注:这个代码是无法成功编译的
namespace hei
{
int rand = 2;
}
namespace hei2
{
int rand = 20;
}
#include<iostream>
using namespace hei;
using namespace hei2;
int main()
{
printf("%d\n",rand);
}
注:这个代码也无法成功编译
为了解决这个问题,出现第三种引入方法
2.3 使用using将命名空间中的成员引入
namespace hei
{
int printf=10;
int rand = 2;
int Add(int a, int b)
{
return a + b;
}
}
#include<iostream>
using hei::rand; //只引入一部分
int main()
{
printf("%d\n",rand);
}
输出:2
注:这种方法可以防止命名的污染,因为他只引入了一部分
三、 C++中的输入与输出
在C++里有cin标准输入和cout标准输出
需要包含头文件iostream以及std标准命名空间
#include<iostream>
using namespace std;
int main()
{
int c;
cin>>c;
cout<<c<<endl;
return 0;
}
//输入 5
//输出 5
C++ 的输入和输出比C语言更方便
因为C++的输入输出不需要控制格式
需要注意的是cin的输入特点
它与C语言中的gets有点像
cin遇到空格、tab或者换行符作为分隔符的
#include<iostream>
using namespace std;
int main()
{
char arr[10];
cin>>arr;
cout<<arr<<endl;
}
//输入 hello world
//输出 hello
四、缺省函数
缺省函数是声明或者定义函数时为函数的参数指定一个默认值
在调用该函数的时候,如果没有指定实参则采用该默认值,否则使用指定的实参
#include<iostream>
using namespace std;
void func(int a=8){
cout<<2*a<<endl;
}
int main()
{
func(10);
func();
return 0;
}
//输出 20
//输出 16
1.全缺省函数
即函数的全部形参都设置为缺省函数
2.半缺省函数
注意:半缺省函数的参数必须从右往左依次给出
注意:缺省函数不能在函数声明和定义中同时出现
注意:缺省值 必须是 常量或者全局变量
五、函数重载
函数重载:函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数
这些同名函数的形参列表(参数个数 类型 顺序)必须不同
来处理实现功能类似 数据类型不同的问题
#include<iostream>
using namespace std;
int Add(int a,int b){
return a+b;
}
double Add(double a,double b){
return a+b;
}
int main()
{
cout<<Add(3,4)<<endl; //打印3+4的结果
cout<<Add(3.3,4.4)<<endl; //打印3.3+4.4的结果
return 0;
}
//输出:7
//输出:7.7
注意:如果仅仅只有返回值不一样 其他都是一样的 是不构成重载的
1.函数重载的原理
C++支持函数重载
但是C语言是不支持的
在运行到执行文件前
要经过:预编译,编译,汇编,链接 这些阶段
其原因其实在C的编译器和C++编译器对函数名修饰的不同
在gcc下修饰规则是<_Z+函数长度+函数名+类型首字母>
这同时也说明了函数的返回类型不同并不会构成函数重载的原因是修饰规则不受返回值影响
六、引用
1.引用的定义
类型 &引用名 = 已定义的变量名
引用与其所代表的变量共享同一个内存单元
相当于给已存在变量取了一个别名
编译系统使引用和其所代表的变量具有相同的地址
#include <iostream>
using namespace std;
int main()
{
int i = 10;
int &j = i;
cout << "i = " << i << " j = " << j << endl;
cout << "i的地址为 " << &i << endl;
cout << "j的地址为 " << &j << endl;
return 0;
}
2.引用的特征
2.1 引用在定义时必须初始化
#include<stdio.h>
using namespace std;
int main()
{
/*正确范例*/
int a=10;
int &b=a;
/*错误范例*/
int a=10;
int &b; //定义时未初始化
b=a;
}
2.2 一个变量可以有多个引用
int a = 10;
int& b = a;
int& c = a;
int& d = a;
2.3 引用一旦引用了一个实体,就不能再引用其他实体
#include<iostream>
using namespace std;
int main()
{
int a=10;
int c=20;
int &b=a;
/*本意:想让b转为引用c*/
b=c;
cout<<a<<endl;
}
虽然没有报错
但b并没有转而引用c
而是将c的值赋给了b 而b又是a的引用 所以a的值被改变为20
3.常引用
上面提到,引用类型必须和引用实体是同种类型的。但是仅仅是同种类型,还不能保证能够引用成功,这儿我们还要注意可否可以修改的问题。
4.引用的使用场景
4.1 引用做参数
可以参考交换函数,学习了引用后,可以不用使用指针作为形参
因为将引用改变以后他们所引用的值也改变了
4.2 引用做返回值
当然引用也能做返回值,但是要特别注意,我们返回的数据不能是函数内部创建的普通局部变量,因为在函数内部定义的普通的局部变量会随着函数调用的结束而被销毁。我们返回的数据必须是被static修饰或者是动态开辟的或者是全局变量等不会随着函数调用的结束而被销毁的数据。
5.引用与指针的区别
1、引用在定义时必须初始化,指针没有要求。
2、引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
3、没有NULL引用,但有NULL指针。
4、在sizeof中的含义不同:引用的结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
5、引用进行自增操作就相当于实体增加1,而指针进行自增操作是指针向后偏移一个类型的大小。
6、有多级指针,但是没有多级引用。
7、访问实体的方式不同,指针需要显示解引用,而引用是编译器自己处理。
8、引用比指针使用起来相对更安全。
9、不能建立引用的引用。不能建立指向引用的指针。引用本身不是一种数据类型,所以没有引用的引用,也没有引用的指针。
七、 类和对象
1.类的声明
类声明中的内容包括数据和函数
分别称为数据成员和成员函数
按照访问权限划分 数据成员和成员函数又分为
公有(public) 私有(private) 保护(protected)
class 类名{
public:
公有数据成员;
公有成员函数;
protected:
保护数据成员;
保护成员函数;
private:
私有数据成员;
私有成员函数;
}; //不要忘记分号!
1、对一个具体的类来讲,类声明格式中的3个部分并非一定要全有,但至少要有其中的一个部。一般情况下,一个类的数据成员应该声明为私有成员,成员函数声明为共有成员。这样,内部的数据整个隐蔽在类中,在类的外部根本就无法看到,使数据得到有效的保护,也不会对该类以外的其余部分造成影响,程序之间的相互作用就被降低到最小。
2、若私有部分处于类的第一部分时,关键字private可以省略。这样,如果一个类体中没有一个访问权限关键字,则其中的数据成员和成员函数都默认为私有的。
3、不能在类声明中给数据成员赋初值。
2.成员函数的定义
2.1 普通成员函数
在类的声明里只给出成员函数的声明
而成员函数的定义写在类的外部
返回值类型 类名::成员函数名(参数表){函数体}
class Score{
public:
void setScore(int m, int f); //建立分数的函数
void showScore(); //显示分数的函数
private:
int mid_exam;
int fin_exam; //内部数据
};
void Score::setScore(int m, int f) //定义内部数据的函数
{
mid_exam = m;
fin_exam = f;
}
void Score::showScore() //访问内部数据的函数
{
cout << "期中成绩: " << mid_exam << endl;
cout << "期末成绩:" << fin_exam << endl;
}
2.2 内联函数
概念:以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。
隐式声明
class Score{
public:
void setScore(int m, int f)
{
mid_exam = m;
fin_exam = f;
}
void showScore()
{
cout << "期中成绩: " << mid_exam << endl;
cout << "期末成绩:" << fin_exam << endl;
}
private:
int mid_exam;
int fin_exam;
};
显式声明
class Score{
public:
inline void setScore(int m, int f);
inline void showScore();
private:
int mid_exam;
int fin_exam;
};
inline void Score::setScore(int m, int f)
{
mid_exam = m;
fin_exam = f;
}
inline void Score::showScore()
{
cout << "期中成绩: " << mid_exam << endl;
cout << "期末成绩:" << fin_exam << endl;
}
1、inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长/递归的函数不适宜使用作为内联函数。
2、inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内代码比较长/递归等等,编译器优化时会忽略掉内联。
3、inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
3.类的实例化 (创建对象)
3.1 在声明类的同时 直接定义
class Score{
public:
void setScore(int m, int f);
void showScore();
private:
int mid_exam;
int fin_exam;
}op1, op2;
3.2 声明了类之后 在使用时再定义对象
Score op1, op2;
3.3 说明
在类内部的成员可以通过成员函数直接访问
但是类的外部不能访问对象的私有成员
在定义对象时,若定义的是指向此对象的指针变量
则访问此对象的成员时 不能用 “.” 操作符
而要使用 “->”操作符(结构体)
Score op, *sc; sc = &op; sc->setScore(99, 100); op.showScore();
4.类的作用域
私有成员只能被类中的成员函数访问,不能在类的外部,通过类的对象进行访问。
一般来说,公有成员是类的对外接口,而私有成员是类的内部数据和内部实现,不希望外界访问。将类的成员划分为不同的访问级别有两个好处:一是信息隐蔽,即实现封装,将类的内部数据与内部实现和外部接口分开,这样使该类的外部程序不需要了解类的详细实现;二是数据保护,即将类的重要信息保护起来,以免其他程序进行不恰当的修改。
5.构造函数
构造函数是一种特殊的成员函数,它主要用于为对象分配空间,进行初始化。
构造函数的名字必须与类名相同,而不能由用户任意命名。
它可以有任意类型的参数,但不能具有返回值。
它不需要用户来调用,而是在建立对象时自动执行。
class Score{
public:
Score(int m, int f); //构造函数
void setScore(int m, int f);
void showScore();
private:
int mid_exam;
int fin_exam;
};
Score::Score(int m, int f)
{
mid_exam = m;
fin_exam = f;
}
创建对象时,编译器自动调用构造函数
类名 对象名[(实参表)]
Score op1(99, 100);
op1.showScore();
带默认参数的构造函数
#include <iostream>
using namespace std;
class Score{
public:
Score(int m = 0, int f = 0); //带默认参数的构造函数
void setScore(int m, int f);
void showScore();
private:
int mid_exam;
int fin_exam;
};
Score::Score(int m, int f) : mid_exam(m), fin_exam(f)
{
cout << "构造函数使用中..." << endl;
}
void Score::setScore(int m, int f)
{
mid_exam = m;
fin_exam = f;
}
void Score::showScore()
{
cout << "期中成绩: " << mid_exam << endl;
cout << "期末成绩:" << fin_exam << endl;
}
int main()
{
Score op1(99, 100);
Score op2(88);
Score op3;
op1.showScore();
op2.showScore();
op3.showScore();
return 0;
}
输出:
构造函数可以重载
class Date
{
public:
Date (){} // 无参构造函数
Date(int year = 0, int month = 1, int day = 1) // 构造函数
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
void TestDate()
{
Date d1; // 调用无参构造函数
Date d2 (2015, 1, 1); // 调用带参的构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
Date d3();
}
但是需要注意的是:
上面这个代码是有问题滴,Date d1,一个没有参数的类的定义
但是由于有两个构造函数
这两个构造函数一个是无参的
另一个无参会使用自带的参数
编译器无法判断要调用哪一种
所以是会出问题的
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成输出:系统自己的初始化
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且 默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
6.析构函数
6.1 析构函数的定义
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。
而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
6.2 析构函数的特性
1.析构函数与构造函数名字相同 在前面加一个波浪号(~)
2.析构函数没有参数 没有返回值 也不能被重载
3.一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
编译器自动生成的析构函数机制:
编译器自动生成的析构函数对内置类型不做处理。
对于自定义类型,编译器会再去调用它们自己的默认析构函数。4.先构造的后析构,后构造的先析构