目录
一、前置内容
1.头文件
2.命名空间
3.函数重载
二、类与对象
1.定义
2.构造器与析构器
3.this指针
4.类的继承
5.访问控制
6.覆盖方法和重载方法
7.友元关系
8.静态属性与静态方法
9.虚方法与抽象类
10.运算符重载
11.多继承和虚继承
12.高级强制类型转换
三、进阶内容
1.异常处理
2.内存管理
3.命名空间和模块化编程
4.链接和作用域
5.模板
6.容器和算法
c++头文件与C语言有所不同,例如要在进行文件处理,在C语言源代码文件中只要包含
c++标准库所使用的的标识符都是在同一个特殊的命名空间(std)中来定义的,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。
#include
void print(int i)
{
std::cout<<"整数为:"<
整数为:5
浮点数为:5.5
C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。
类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。
类的定义:
(方法也可以在类的外部使用范围解析运算符 :: 定义)
方法的定义:类名 方法名,例如Cat Mimi;
①构造器:
类的构造器是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
②析构器:
类的析构器是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
下图通过this指针实现了同名变量的初始化。
class Human
{
char man;
Human(char man);
};
Human::Human(char man)
{
this->man = man;
}
继承是面向对象编程技术的一个核心概念,通过继承机制,程序员可以对现有的代码进行进一步的扩展并应用在新的程序中。
①继承方法
class 类名 : 访问修饰符 父类名{};例如class Cat : public Animal{};
②继承机制中的构造器与析构器
子类的构造器与析构器可以继承于父类,例如Cat::Cat(std::string Name) : Animal(Name)
#include
class Animal
{
public:
Animal(std::string theName)
{
name = theName;
};
void print()
{
std::cout<<"The Name is:"<
也可对子类的构造器和析构器进行重写,执行顺序为外内外,即父类构造器->子类构造器->子类方法->子类析构器->父类析构器。
数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 public、protected、private来指定的。关键字 public、protected、private称为访问修饰符。成员和类的默认访问修饰符是 private。
①访问修饰符
共有(public)成员:公有成员在程序中类的外部是可访问的,可以不使用任何成员函数来设置和获取公有变量的值。
私有(private)成员:私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。
受保护(protected)成员:受保护成员变量或函数与私有成员十分相似,但有一点不同,受保护成员在子类中是可访问的。
②继承中的特点
有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。
1.public 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public, protected, private
2.protected 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected, protected, private
3.private 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:private, private, private
C++中子类可对父类的方法进行覆盖和重载,方法与前文讲的函数重载类似。值得一提的是方法的重载只能在父类中进行,若在子类中尝试重载将会对父类方法进行覆盖。
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,如下所示:
class Cat
{
private:
std::string name;
public:
friend void printCatName();
friend class Dog;
};
我们可以使用 static 关键字来把类成员定义为静态的。
①静态属性
静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化。
②静态方法
静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。静态成员函数有一个类范围,他们不能访问类的 this 指针。
①虚方法
C++提供new和delete两个保留字,通过new可以在没有创建变量的情况下为有关数据分配内存,例如:
int *pointer = new int;
*pointer = 100;
std::cout<<"The *pointer is:"<<*pointer<
The *pointer is:100
注意:在删除掉pointer指向的内容之后,pointer指针仍然存在,因此需要将pointer指向NULL进而删除pointer指针。
但是对于通过new来创建对象会出现一些问题,如下所示:
class Pet
{
public:
void eat()
{
std::cout<<"我是宠物,我没有特定的食物。"<eat();
delete cat;
return 0;
}
我是宠物,我没有特定的食物。
之所以出现这种情况是因为new在程序运行时才会为cat分配Cat类型的指针,这和编译时的类型是不一样的,因此为正确地调用发放应该把这些方法声明为虚方法,即在函数前加virtual。值得一提的是虚方法是继承的,因此无法在子类中再次将已经声明为虚方法的函数更改为非虚方法。
②抽象类
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 "= 0" 来指定的。
设计抽象类的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。如果试图实例化一个抽象类的对象,会导致编译错误。
C++除了一下五个运算符无法重载,其他的运算符均可重载。
重载运算符的一般格式:
函数类型 operator 运算符名称(形参表列)
{
对运算符的重载处理
}
如:
int operator+(int a,int b)
{
return(a-b);
}
下面是类的运算符重载实例:
class Complex
{
public:
Complex()
{
real = image = 0;
}
Complex(int a,int b)
{
real = a;
image = b;
}
Complex operator+(Complex rhs)
{
return Complex(real+rhs.real,image+rhs.image);
}
void print()
{
std::cout<
上述运算符重载也可以通过友元函数实现。
我们也可以对<<操作符进行重载,形式为:
std::ostream&operator<<(std::ostream&os,数据)
第一个输入参数os是将要向他写数据的那个流,它是以“引用传递”方式传递的。
下面是对上面例题的改进:
class Complex
{
public:
Complex()
{
real = image = 0;
}
Complex(int a,int b)
{
real = a;
image = b;
}
Complex operator+(Complex rhs)
{
return Complex(real+rhs.real,image+rhs.image);
}
friend std::ostream&operator<<(std::ostream&os,Complex data)
{
os << data.real << "+" << data.image << "i" ;
return os;
}
private:
int real;
int image;
};
int main()
{
Complex a(2,3),b(-3,1),c;
c = a + b;
std::cout<< c << std::endl;
}
①多继承
多继承可以使子类继承多个父类,形式为:class 子类名:访问修饰符 父类名1,访问修饰符 父类名2 ...{...};
②虚继承
通过虚继承某个基类,就是在告诉某个编译器:从当前这个基类再派生出来的子类只能拥有那个基类的一个实例,形式为:在访问修饰符前virtual。
与C语言中强制类型转换不同,c++提供动态对象强制类型转换。
climits头文件定义的常用的符号常量:
CHAR_MIN | SHRT_MAX | UINT_MIN | FLT_MAX |
char的最小值 | short 最大值 | unsigned int 最小值 | float类型正数最大值 |
其他的以此类推。
assert函数包含在头文件cassert中,assert()需要传入一个参数,如果参数为真则跳过,参数为假则抛出异常。
异常捕获的基本语法如下:
try
{
//Do something
//Throw an exception on error
}
catch
{
//Do whatever
}
每条try语句至少有一个配对的catch语句。
在某个try语句中执行过throw语句,它try内后面所有语句将永远不会被执行。
我们可以在定义函数时使用以下语法明确抛出异常的类型:
type functionName(arguments) throw(type);
若没有使用这种形式则默认可以抛出任意类型的异常。
以下为实例:
int main()
{
try
{
int num;
std::cout<<"请输入一个正整数:";
std::cin>>num;
if(num<=0)
throw "输入的不是正整数!";
else std::cout<<"你输入的数字为:"<
我们可以通过new关键字来生成动态数组,形式为int *数组名 = new 类型名[大小],使用完成后应该通过delete []数组名 来删除数组,例如:
unsigned int num;
std::cout<<"Please input:";
std::cin>>num;
int *x = new int[num];
for(int i=0;i
Please input:4
x[0]:0
x[1]:1
x[2]:2
x[3]:3
我们可以通过new来从函数或方法中返回内存,例如:
int *newInt(int value)
{
int *myInt = new int;
*myInt = value;
return myInt;
}
int main()
{
int *x = newInt(20);
std::cout<< "*x = "<<*x;
delete x;
x = NULL;
return 0;
}
//*x = 20
函数不应该返回一个指向局部变量的指针,因为局部变量在函数结束后会自动释放,如果想要在不留隐患的前提下返回一个指针,那它只能是一个动态分配的内存块的基地址。
我们在对象的赋值时如果存在指针变量会将其原样赋值,但是当我们删除其中一个对象时它包含的指针也会被删除,如果另一个对象还在引用这一个指针,那么就会出现问题,其中一种解决办法就是利用运算符重载来重载等号。
由于使用运算符重载过于复杂,因此c++提供副本构造器来精确控制复制什么和如何复制。
在创建实例时进行赋值会实现副本构造器的使用,例如:
MyClass obj1;
MyClass obj2 = obj1;
副本构造器的形式为:MyClass(const MyClass &rhs)
与C语言相同,C++中也可以导入自己的头文件,文件名用双引号括起来,形式为:#include "name.h"。
#include ".\\name.h"表示当前目录,#include "dirc\\name.h"表示当前目录中dirc中的子目录,#include "..\\name.h"表示上一级目录。
通过c++预处理器,我们可以让头文件只在这个类还没有被声明的情况下声明它。
#ifdef ClassOne
#define ClassOne
class myClass{...};
#endif
创建命名空间的格式:
namespace myNameSpace{...}
命名空间可以使你使用同一个标识符而不会冲突,如果某个东西是在命名空间中定义的,程序将不能立即使用它,如果访问命名空间有三种方式:
1. myNameSpace::XXX
2. using namespace myNameSpace;
3.using myNameSpace::XXX
每个源文件都被称为一个翻译单元(translation unit),在某一个翻译单元里定义的东西在另一个翻译单元里使用正是链接发挥作用的地方。
链接分为三种:
1.外链接(external):每个翻译单元都可以访问这个东西。
2.内链接(internal):在某个翻译单元里定义的东西只能在翻译单元里使用(在任何函数以外定义的静态变量都有内链接)。
3.无链接( none):在函数里定义的变量只存在于该函数的内部,根本没有任何链接。
至此我们学过面向过程和面向对象两种设计范性,现在再介绍另一种范性:泛型编程。
泛型编程支持程序员创建函数和类的蓝图(即模板),而不是具体的函数和类。
这些模板可以没有任何类型,当程序需要用到这些函数的某一个时,编译器会根据模板即时生成一个能够对特定数据类型进行处理的代码版本。
我们只要使用一个占位符(通常使用字符T表示),然后用这个占位符编写函数和类就可以。
在函数定义之前我们需要使用template
template
void exchange(T &a,T &b)
{
T temp = a;
a = b;
b = temp;
}
int main()
{
int a=111,b=222;
std::string x="abc",y="def";
exchange(a,b);
exchange(x,y);
std::cout<<"a="<
我们还可以使用exchange
类模板的写法与函数模板类似,但是构造器的实现需要使用MyClass
定义实例时也需要使用MyClass
template
class Stack
{
public:
Stack(unsigned int size = 100)
{
this -> size = size;
data = new T[size];
sp = 0;
}
~Stack()
{
delete []data;
}
void push(T value)
{
data[sp++] = value;
}
T pop()
{
return data[--sp];
}
private:
unsigned int size;
unsigned int sp;
T *data;
};
int main()
{
Stack intStack(100);
intStack.push(1);
intStack.push(2);
intStack.push(3);
std::cout<
内联函数从源代码层看,有函数的结构,而在编译后,却不具有函数的性质。编译时,类似宏替换,使用函数体替换调用处的函数名,一般在代码中用inline修饰,如:
inline int add(int a,int b)
{
return a+b;
}
STL是世界上第一个免费的基于模板的c++库,它为程序员提供了一些非常有用的c++算法和容器。
C++标准库提供的向量(vector)类型从根本上解决了数组先天不足的问题。
我们可以通过std::vector
向量可以动态地随着往里面添加元素而无限增大,还可以用它的size()方法得到点前长度(当前包含的元素的个数),用push_back()方法往里边添加东西,也可以用访问数组元素的语法来访问某给定向量的各个元素。
以下是一个例子:
#include
#include
int main()
{
std::vector nums;
for(int i=0;i<5;i++)
nums.push_back(i);
for(int i=0;i
c++标准库提供各种迭代器(iterator)来对容器的各个元素进行遍历。
以下是对上述例子的更改:
int main()
{
std::vector nums;
for(int i=0;i<5;i++)
nums.push_back(i);
std::vector::iterator iter = nums.begin();
while(iter != nums.end())
{
std::cout<< *iter<<",";
iter++;
}
return 0;
}
//0,1,2,3,4,
C语言提供一个经过全面优化的排序算法,它包含在algorithm头文件里面。
我们只需要调用sort()方法按照std::sort(beginIterator,endIterator);的形式就可以使用了。
以下是一个例子:
int main()
{
std::vector nums;
nums.push_back(23);
nums.push_back(56);
nums.push_back(10);
nums.push_back(4);
std::sort(nums.begin(),nums.end());
std::vector::iterator iter = nums.begin();
while(iter != nums.end())
{
std::cout<< *iter<<",";
iter++;
}
return 0;
}
//4,10,23,56,