一、前置内容
1.头文件
c++头文件与C语言有所不同,例如要在进行文件处理,在C语言源代码文件中只要包含<stdio.h>,而在 C++ 源代码文件中需要包含头文件 和 。
2.命名空间
c++标准库所使用的的标识符都是在同一个特殊的命名空间(std)中来定义的,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。
3.函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。
#include
void print(int i)
{
std::cout<<“整数为:”<<i<<std::endl;
}
void print(double j)
{
std::cout<<“浮点数为:”<<j<<std::endl;
}
int main()
{
print(5);
print(5.5);
return 0;
}
整数为:5
浮点数为:5.5
二、类与对象
C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。
类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。
1.定义
类的定义:
(方法也可以在类的外部使用范围解析运算符 :: 定义)
方法的定义:类名 方法名,例如Cat Mimi;
2.构造器与析构器
①构造器:
类的构造器是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
②析构器:
类的析构器是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
3.this指针
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
下图通过this指针实现了同名变量的初始化。
class Human
{
char man;
Human(char man);
};
Human::Human(char man)
{
this->man = man;
}
4.类的继承
继承是面向对象编程技术的一个核心概念,通过继承机制,程序员可以对现有的代码进行进一步的扩展并应用在新的程序中。
①继承方法
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:”<<name<<std::endl;
}
std::string name;
};
class Cat : public Animal
{
public:
Cat(std::string Name) : Animal(Name){};
};
int main()
{
Cat cat(“mimi”);
cat.print();
return 0;
}
也可对子类的构造器和析构器进行重写,执行顺序为外内外,即父类构造器->子类构造器->子类方法->子类析构器->父类析构器。
5.访问控制
数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 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
6.覆盖方法和重载方法
C++中子类可对父类的方法进行覆盖和重载,方法与前文讲的函数重载类似。值得一提的是方法的重载只能在父类中进行,若在子类中尝试重载将会对父类方法进行覆盖。
7.友元关系
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,如下所示:
class Cat
{
private:
std::string name;
public:
friend void printCatName();
friend class Dog;
};
8.静态属性与静态方法
我们可以使用 static 关键字来把类成员定义为静态的。
①静态属性
静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化。
②静态方法
静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。静态成员函数有一个类范围,他们不能访问类的 this 指针。
9.虚方法与抽象类
①虚方法
C++提供new和delete两个保留字,通过new可以在没有创建变量的情况下为有关数据分配内存,例如:
int *pointer = new int;
*pointer = 100;
std::cout<<“The *pointer is:”<<*pointer<<std::endl;
delete pointer;
pointer = NULL;
The *pointer is:100
注意:在删除掉pointer指向的内容之后,pointer指针仍然存在,因此需要将pointer指向NULL进而删除pointer指针。
但是对于通过new来创建对象会出现一些问题,如下所示:
class Pet
{
public:
void eat()
{
std::cout<<“我是宠物,我没有特定的食物。”<<std::endl;
}
};
class Cat : public Pet
{
public:
void eat()
{
std::cout<<“我是猫,我吃鱼。”<<std::endl;
}
};
int main()
{
Pet *cat = new Cat;
cat->eat();
delete cat;
return 0;
}
我是宠物,我没有特定的食物。
之所以出现这种情况是因为new在程序运行时才会为cat分配Cat类型的指针,这和编译时的类型是不一样的,因此为正确地调用发放应该把这些方法声明为虚方法,即在函数前加virtual。值得一提的是虚方法是继承的,因此无法在子类中再次将已经声明为虚方法的函数更改为非虚方法。
②抽象类
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 “= 0” 来指定的。
设计抽象类的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。如果试图实例化一个抽象类的对象,会导致编译错误。
10.运算符重载
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<<real<<“+”<<image<<“i”<<std::endl;
}
private:
int real;
int image;
};
int main()
{
Complex a(2,3),b(-3,1),c;
c = a + b;
c.print(); //结果为-1+4i
}
上述运算符重载也可以通过友元函数实现。
我们也可以对<<操作符进行重载,形式为:
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;
}
11.多继承和虚继承
①多继承
多继承可以使子类继承多个父类,形式为:class 子类名:访问修饰符 父类名1,访问修饰符 父类名2 …{…};
②虚继承
通过虚继承某个基类,就是在告诉某个编译器:从当前这个基类再派生出来的子类只能拥有那个基类的一个实例,形式为:在访问修饰符前virtual。
12.高级强制类型转换
与C语言中强制类型转换不同,c++提供动态对象强制类型转换。
三、进阶内容
1.异常处理
①climits头文件
climits头文件定义的常用的符号常量:
CHAR_MIN SHRT_MAX UINT_MIN FLT_MAX
char的最小值 short 最大值 unsigned int 最小值 float类型正数最大值
其他的以此类推。
②assert函数
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<<“你输入的数字为:”<<num<<std::endl;
}
catch (const char *c)
{
std::cout<< c<<std::endl;
}
return 0;
}
//请输入一个正整数:-1
//输入的不是正整数!
2.内存管理
①动态数组
我们可以通过new关键字来生成动态数组,形式为int *数组名 = new 类型名[大小],使用完成后应该通过delete 数组名 来删除数组,例如:
unsigned int num;
std::cout<<“Please input:”;
std::cin>>num;
int *x = new int[num];
for(int i=0;i<num;i++)
x[i]=i;
for(int i=0;i<num;i++)
std::cout<<“x[”<<i<<“]:”<<x[i]<<std::endl;
delete x;
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;