C++基础教程 2020.5.27
g++ -v —查看 gcc 版本/是否安装 gcc
编译 C++
g++ test.cpp test1.cpp -o test
使用 gcc 来编译 C++文件,test.cpp、test1.cpp 是代码,后面接的-o test
是编译到的文件可直接执行,不指定-o 默认会创建 a.out,也可执行。
指定编译 C++的 gcc 版本
g++ -g -Wall -std=c++11 test.cpp -o test
这里指定为 c++11 版本
cout << “hello world” << endl; —输出 hello world 并且换行
C++ Main 方法模板代码
#include <iostream>
using namespace std;
int main(){
cout << "hello world" << endl; //输出hello world并且换行
return 0; //程序结束
}
typedef —命名类型,例如:typedef string text;
枚举类型
enum color{red, green = 5, blue} //red为0、green为5、blue为6
函数定义
#include <iostream>
using namespace std;
void func(); //声明函数
int main(){
func(); //调用函数
}
void func(){ //函数定义
cout << "hello world" << endl;
}
定义常量
- #define NAME “QIFEN”
- const int AGE = 20;
修饰符类型
修饰符signed、unsigned、long 和 short可应用于整型
,signed 和 unsigned 可应用于字符型
,long 可应用于双精度型
。
修饰符 signed 和 unsigned 也可以作为 long 或 short 修饰符的前缀。例如:unsigned long int。
C++存储类
- auto 存储类:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。
- register 存储类:用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小
- static 存储类:指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
- extern 存储类:用来在另一个文件中声明一个全局变量或函数,通常用于当有两个或多个文件共享相同的全局变量或函数的时候。
- mutable 存储类:说明符仅适用于类的对象,它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。
- thread_local 存储类:使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。thread_local 说明符可以与 static 或 extern 合并。thread_local 不能用于函数声明或定义。
函数参数
- 传值调用:该方法把参数的实际值赋值给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。默认
- 指针调用:该方法把参数的地址赋值给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
- 引用调用:该方法把参数的引用赋值给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
Lambda 函数与表达式
语法:
[capture](parameters)->return-type{body} //语法
[](int x, int y) -> { return x > y; } //案例
[capture](parameters){body} //无参数的函数lambda语法
[]{ ++global_x } //无参数的函数案例
[](int x, int y)->int{ int z = x + y; return z + x; } //复杂案例
参数传递:
[] // 沒有定义任何变量。使用未定义变量会引发错误。
[x, &y] // x以传值方式传入(默认),y以引用方式传入。
[&] // 任何被使用到的外部变量都隐式地以引用方式加以引用。
[=] // 任何被使用到的外部变量都隐式地以传值方式加以引用。
[&, x] // x显式地以传值方式加以引用。其余变量以引用方式加以引用。
[=, &z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用。
随机数
#include <iostream>
using namespace std;
int main()
{
srand((unsigned)time(NULL)); //设置种子
int j = rand(); //产生随机数
cout << j << endl;
return 0;
}
C++数组
声明数组:
type arrayName[arraySize]; //声明一个数组的语法
double arr[10]; //案例:定义一个double类型名为arr的包含10个元素的数组
double balance[] = {1000.0, 2.0, 3.0, 7.4, 50.0}; //初始化元素
balance[3] = 6.6; //把索引为3的数组内容改为6.6
案例:
#include <iostream>
using namespace std;
#include <iomanip>
using std::setw; //引用设置格式化的函数
int main()
{
int a[] = {1, 2, 3, 4};
for (int i = 0; i <= 3; i++)
{
cout << setw(10) << i << setw(10) << a[i] << endl; //setw(10)格式化10个空格
}
return 0;
}
C++字符串
char greeting[6] = {'h', 'e', 'l', 'l', 'o', '\0'}; //手动使用\0(null)结束数组,否则将会自动加上
char greeting[] = "hello"; //上面的语句可写成这样,并且效果相同
#include <cstring>
头文件,需要定义在程序开始的地方,定以后可使用下面的函数
函数 | 用途 |
---|---|
strcap(s1, s2); | 复制字符串 s2 到字符串 s1 |
strcat(s1, s2); | 连接字符串 s2 到 s1 的末尾 |
strlen(s1); | 返回字符串 s1 的长度 |
strcmp(s1, s2); | 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回值小于 0;如果 s1>s2 则返回值大于 0。 |
strchr(s1, ch); | 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
strstr(s1, s2); | 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |
C++指针
使用cout << &var << endl
打印 var 变量的地址
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。
type *var-name; //指针声明
int *age; //int类型指针
char *ch; //char类型指针
所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
使用 C++指针
#include <iostream>
using namespace std;
int main(){
int var = 20; //实际变量声明
int *ip; //指针变量的声明
ip = &var; //在指针变量中存储var的地址
cout << "变量值:" << var << endl;
cout << "指针变量中存储的地址:" << ip << endl;
cout << "指针中地址的值:" << *ip << endl;
return 0;
}
输出:
变量值:20
指针变量中存储的地址:0x7ffcb6e593ac
指针中地址的值:20
C++引用
引用变量是一个别名,也就是说,它是某个已存在的变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
C++引用和指针不同的地方
- 不存在空引用。引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另外一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
C++中创建引用
#include <iostream>
using namespace std;
int main(){
//声明简单的变量
int i;
double d;
//声明引用变量
int& r = i; //&读作引用,读作“r是一个初始化为i的整形引用”
double& s = d;
i = 5;
cout << "i的值为:" << i << endl;
cout << "r(引用i)的值为:" << r << endl;
d = 11.3;
cout << "d的值为:" << d << endl;
cout << "s(引用d)的值为:" << s << endl;
return 0;
}
输出:
i的值为:5
r(引用i)的值为:5
d的值为:11.3
s(引用d)的值为:11.3
C++中支持把引用作为参数和返回值
C++日期&时间
在 C++程序中继承了 C 语言用于日期和时间操作的结构和函数,引用头文件来使用日期和时间相关的函数和结构。结构类型 tm 把日期和时间以 C 结构的形式保存,tm 结构的定义如下:
struct tm {
int tm_sec; // 秒,正常范围从 0 到 59,但允许至 61
int tm_min; // 分,范围从 0 到 59
int tm_hour; // 小时,范围从 0 到 23
int tm_mday; // 一月中的第几天,范围从 1 到 31
int tm_mon; // 月,范围从 0 到 11
int tm_year; // 自 1900 年起的年数
int tm_wday; // 一周中的第几天,范围从 0 到 6,从星期日算起
int tm_yday; // 一年中的第几天,范围从 0 到 365,从 1 月 1 日算起
int tm_isdst; // 夏令时
}
案例:当前日期和时间
#include <iostream>
#include <ctime>
using namespace std;
int main()
{
time_t now = time(0); //基于当前系统的当前日期/时间
char *dt = ctime(&now); //把now转换为字符串形式
tm *ltm = localtime(&now); //获取时间结构的指针
cout << "本地日期时间:" << dt << endl;
cout << "1970到目前时间:" << now << endl;
cout << "年:" << 1900 + ltm->tm_year << endl; //使用->运算符来访问结构成员
cout << "月:" << 1 + ltm->tm_mon << endl;
cout << "日:" << ltm->tm_mday << endl;
cout << "时间:" << ltm->tm_hour << ":" << ltm->tm_min << ":" << ltm->tm_sec << endl;
return 0;
}
输出:
本地日期时间:Fri May 22 21:05:40 2020
1970到目前时间:1590152740
年:2020
月:5
日:22
时间:21:5:40
C++基本的输入和输出
I/O 库头文件
头文件 | 函数和描述 |
---|---|
<iostream> | 该文件定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。 |
<iomanip> | 该文件通过所谓的参数化的流操纵器(比如 setw 和 setprecision),来声明对执行标准化 I/O 有用的服务。 |
<fstream> | 该文件为用户控制的文件处理声明服务。我们将在文件和流的相关章节讨论它的细节。 |
案例:
#include <iostream>
using namespace std;
int main(){
char str[] = "hello c++";
char name[60];
cin >> name; //标准输入流(cin)
cout << str << name << endl; //标准输出流(cout)
cerr << "error stream" << endl; //标准错误流(cerr)
clog << "this is log" << endl; //标准日志流(clog)
}
C++数据结构
C/C++数组允许定义可存储相同类型数据项的变量,但是结构是 C++中另一种用户自定义的可用的数据类型,它允许存储不同类型的数据项。
定义结构,语法如下:
struct type_name{ //结构体类名
member_type1 member_name1; //变量
member_type2 member_name2;
member_type3 member_name3;
} object_names; //结构变量
案例:
struct Books{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
访问结构成员,使用成员访问运算符(.)
```c++
#include <iostream>
#include <cstring>
using namespace std;
void printBook(struct Books book); //声明函数
struct Books
{ //定义数据结构
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main()
{
Books book; //定义结构体类型 Books的变量
strcpy(book.title, "C++");
strcpy(book.author, "qifen");
strcpy(book.subject, "lang");
book.book_id = 12345;
printBook(book);
return 0;
}
void printBook(struct Books book)
{ //结构作为函数参数
cout << "标题:" << book.title << endl;
cout << "作者:" << book.author << endl;
cout << "类目:" << book.subject << endl;
cout << "ID:" << book.book_id << endl;
}
输出:
标题:C++
作者:qifen
类目:lang
ID:12345
定义指向结构的指针,语法:
struct Books *struct_pointer; //定义指向结构的指针
struct_pointer = &book; //获取结构变量的地址
struct_pointer->title; //使用指向该结构的指针访问结构的成员,必须使用->运算符
使用结构指针来重写上面的实例
#include <iostream>
#include <cstring>
using namespace std;
void printBook(struct Books *book);
struct Books{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main(){
Books book;
strcpy(book.title, "book title");
strcpy(book.author, "book author");
strcpy(book.subject, "book subject");
book.book_id = 20;
printBook(&book);
return 0;
}
void printBook(struct Books *book){
cout << "标题:" << book.title << endl;
cout << "作者:" << book.author << endl;
cout << "类目:" << book.subject << endl;
cout << "ID:" << book.book_id << endl;
}
小结:如果函数传参是指针,需要使用&book
来获取 book 地址然后传给printBook(struct Books *book)
函数,函数接收&book
指向的地址,然后读取指针的结构需要使用book->title
形式。
使用typedef
关键字定义类的“别名”。例如:
typedef struct Books{
char title[50];
char author[50];
char subject[100];
int book_id;
}Books;
Books book1, book2; //使用Books来定义变量
typedef long int *pint32; //使用typedef来定义非结构类型
pint32 x, y, z; //x,y,z都是指向长整形long int的指针。
C++类 & 对象
C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。
C++类定义语法:
class Box{
public: //访问属性的作用域为在类的外部也可访问
double length;
double breadth;
double height;
};
Box box; //声明box,类型为Box
访问数据成员(使用.来访问):
#include <iostream>
using namespace std;
class Box{
public:
double length;
double breadth;
double height;
};
int main(){
Box box;
box.height = 10.0;
box.length = 9.9;
box.breadth = 8.8;
cout << box.length + box.height + box.breadth << endl;
}
C++类访问修饰符
class Base{
public:
//公有成员:外部可读写
protected:
//受保护成员:派生类(子类)可访问
private:
//私有成员:外部不可读写
};
C++类构造函数&析构函数
类的构造函数
是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。案例:
#include <iostream>
using namespace std;
class Line
{ //定义类
public:
void setLength(double len); //成员函数声明
double getLength(void); //无传参数需要写void
Line(double len); //构造函数、随类加载
private:
double length;
};
Line::Line(double len)
{
cout << "initial length = " << len << endl;
length = len;
}
void Line::setLength(double len)
{
length = len;
}
//Line::Line(double len): length(len){} //和上面的构造函数效果相同
double Line::getLength(void)
{
return length;
}
int main()
{
Line line(9.9);
cout << "length of line: " << line.getLength() << endl;
line.setLength(99.99);
cout << "length of line: " << line.getLength() << endl;
return 0;
}
输出:
initial length = 9.9
length of line: 9.9
length of line: 99.99
类的析构函数
是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
#include <iostream>
using namespace std;
class Line
{
public:
Line(); //构造函数声明
~Line(); //析构函数声明
};
Line::Line(void) //对象初始化
{
cout << "Line is initial" << endl;
}
Line::~Line(void) //释放内存
{
cout << "Line is deleted" << endl;
}
int main()
{
Line line;
return 0;
}
拷贝构造函数
是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
- 通过使用另一个同类型的对象来初始化新创建的对象。
- 复制对象把它作为参数传递给函数。
- 复制对象,并从函数返回这个对象。
语法:
classname(canst classname &obj){
//构造函数的主体
}
在这里obj
是一个对象引用,该对象是用于初始化另一个对象的。案例:
#include <iostream>
using namespace std;
class Line
{
public:
int getLength(void);
Line(int len);
Line(const Line &obj); //拷贝构造函数
~Line();
private:
int *ptr;
};
Line::Line(int len)
{ //构造函数
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj)
{ //拷贝构造函数并为指针ptr分配内存
ptr = new int;
*ptr = *obj.ptr; //拷贝值
}
Line::~Line(void)
{ //析构函数释放内存
delete ptr;
}
int Line::getLength(void)
{
return *ptr;
}
void display(Line obj)
{
cout << "line大小: " << obj.getLength() << endl;
}
int main()
{
Line line(10);
display(line);
return 0;
}
类的友元函数
是定义在类外部,但是有权限访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。语法:
class Box{
public:
friend void printWidth(Box box); //声明友元函数,需要加friend关键字
};
friend class ClassName; //声明友元类
友元修饰函数案例:
#include <iostream>
using namespace std;
class Box
{
double width;
public:
friend void printWidth(Box box);
void setWidth(double wid);
};
void Box::setWidth(double wid)
{ //成员函数定义
width = wid;
}
void printWidth(Box box)
{ //定义函数
cout << box.width << endl;
}
int main()
{
Box box;
box.setWidth(9.9);
printWidth(box);
return 0;
}
友元修饰类案例:
#include <iostream>
using namespace std;
class Box
{
double width;
public:
friend void printWidth(Box box);
friend class BigBox;
void setWidth(double wid);
};
class BigBox
{
public :
void Print(int width, Box &box)
{
// BigBox是Box的友元类,它可以直接访问Box类的任何成员
box.setWidth(width);
cout << "Width of box : " << box.width << endl;
}
};
// 成员函数定义
void Box::setWidth(double wid)
{
width = wid;
}
// 请注意:printWidth() 不是任何类的成员函数
void printWidth(Box box)
{
// 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员
cout << "Width of box : " << box.width << endl;
}
// 程序的主函数
int main()
{
Box box;
BigBox big;
// 使用成员函数设置宽度
box.setWidth(10.0);
// 使用友元函数输出宽度
printWidth(box);
// 使用友元类中的方法设置宽度
big.Print(20, box);
return 0;
}
C++内联函数inline
:引入内联函数的目的是为了解决程序中函数调用的效率问题,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是空间代价换时间。所以内联函数一般都是 1-5 行的小函数。在使用内联函数时要注意:
- 在内联函数内不允许使用循环语句和开关语句。
- 内联函数的定义必须出现在内联函数第一次调用之前。
- 类结构中所在的类说明内部定义的函数是内联函数。
案例:
#include <iostream>
using namespace std;
inline int Max(int x, int y){
return (x > y)? x : y;
}
int main(){
cout << "max: " << Max(10, 99) << endl;
return 0;
}
C++ this指针:
在 C++中,每一个对象都能通过this
指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
案例:
#include <iostream>
using namespace std;
class Box
{
public:
Box(double l = 2.0, double b = 2.0, double h = 2.0)
{
length = l;
breadth = b;
height = h;
}
double Volume()
{
return length * breadth * height;
}
int compare(Box box)
{
return this->Volume() > box.Volume(); //this指针指向调用方,使用->读指针值
}
private:
double length;
double breadth;
double height;
};
int main(void)
{
Box box(2.3, 3.4, 4.5);
Box box1(2.2, 3.3, 4.4);
if (box.compare(box1))
{
cout << "box1 is smaller than box" << endl;
}
else
{
cout << "box1 is equal to or larger than box" << endl;
}
return 0;
}
C++访问指向类的指针:
一个指向 C++类的指针与指向结构的指针类似,访问指向类的指针成员,需要使用成员访问运算符->
,就像访问指向结构的指针一样。与所有的指针一样,必须在使用指针之前,对指针进行初始化。
C++类的静态成员:
可以使用static
关键字来把类成员定义为静态的。当我们声明类的成员为静态,这意味着无论创建多少个类的对象,静态成员都只有一个副本。静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化。案例:
#include <iostream>
using namespace std;
class Box{
public:
static int objectCount;
Box(double l = 2.0, double b = 2.0, double h = 2.0){
length = l;
breadth = b;
height = h;
objectCount++;
}
double Volume(){
return length * breadth * height;
}
private:
double length;
double breadth;
double height;
};
int Box::objectCount = 0; //初始化类Box的静态成员
int main(void){
Box box1(1.1, 2.2, 3.3);
Box box2(11.1, 12.2, 13.3);
cout << Box::objectCount << endl;
return 0;
}
静态成员函数
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。
静态成员函数与普通成员函数的区别:
- 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
- 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。
案例:
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount;
// 构造函数定义
Box(double l = 2.0, double b = 2.0, double h = 2.0)
{
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
static int getCount() //静态成员函数访问静态成员数据
{
return objectCount;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
int main(void)
{
// 在创建对象之前输出对象的总数
cout << "Inital Stage Count: " << Box::getCount() << endl;
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
// 在创建对象之后输出对象的总数
cout << "Final Stage Count: " << Box::getCount() << endl;
return 0;
}
C++继承
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类
,新建的类称为派生类
。语法:
class derived-class: access-specifier base-class
其中,访问修饰符 access-specifier 是 public、protected 或 private 其中的一个,base-class 是之前定义过的某个类的名称。如果未使用访问修饰符 access-specifier,则默认为 private。
#include <iostream>
using namespace std;
class Shape{
public:
void setWidth(int w){
width = w;
}
void setHeight(int h){
height = h;
}
protected:
int width;
int height;
};
class Rectangle: public Shape{
public:
int getArea(){
return (width * height);
}
};
int main(void){
Rectangle Rect;
Rect.setWidth(5);
Rect.setHeight(7);
cout << Rect.getArea() << endl;
return 0;
}
继承的访问权限表:
访问 | public | protected | private |
---|---|---|---|
同一个类 | true | true | true |
派生类 | true | true | false |
外部的类 | true | false | false |
一个派生类继承了所有的基类方法,但是这些情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
继承类型:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
多继承:
多继承即一个子类可以有多个父类,它继承了多个父类的特性。语法:
#include <iostream>
using namespace std;
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected: //派生类可访问
int width;
int height;
};
class PaintCost
{
public:
int getCost(int area)
{
return area * 70;
}
};
class Rectangle : public Shape, public PaintCost
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea();
cout << Rect.getArea() << endl;
cout << Rect.getCost(area) << endl;
return 0;
}
虚继承:
多继承中,可以有环状继承,例如:
class D{...};
class B: public D{...};
class A: public D{...};
class C: public B, public A{...};
这个继承会使 D 创建两个对象,要解决这个问题就需要使用虚拟继承格式class 类名: virtual 继承方式 父类名
。虚继承在创建对象的时候会创建一个虚表(创建父类对象的时候)。语法:
class D{...};
class B: virtual public D{...};
class A: virtual public D{...};
class C: public B, public A{...};
案例:
#include <iostream>
using namespace std;
//基类
class D
{
public:
D(){cout<<"D()"<<endl;}
~D(){cout<<"~D()"<<endl;}
protected:
int d;
};
class B:virtual public D
{
public:
B(){cout<<"B()"<<endl;}
~B(){cout<<"~B()"<<endl;}
protected:
int b;
};
class A:virtual public D
{
public:
A(){cout<<"A()"<<endl;}
~A(){cout<<"~A()"<<endl;}
protected:
int a;
};
class C:public B, public A
{
public:
C(){cout<<"C()"<<endl;}
~C(){cout<<"~C()"<<endl;}
protected:
int c;
};
int main()
{
cout << "Hello World!" << endl;
C c; //D, B, A ,C
cout<<sizeof(c)<<endl;
return 0;
}
输出:
Hello World!
D()
B()
A()
C()
40
~C()
~A()
~B()
~D()
总结:运行时先初始化最里层基类,结束时先结束最外层派生类,派生类继承同一个基类需要在基类上添加virtual
关键字。
C++重载运算符和重载函数
C++允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同的名称和声明,但是它们的参数列表和实现可以不同。案例:
#include <iostream>
using namespace std;
class printData
{
public:
void print(int num)
{
cout << num << endl;
}
void print(double num)
{
cout << num << endl;
}
void print(char str[])
{
cout << str << endl;
}
};
int main(void)
{
printData pd;
pd.print(6);
pd.print(6.6);
char c[] = "this is text";
pd.print(c);
return 0;
}
运算符重载:
重载的运算符是带有特殊名称的函数,函数名是由关键字operator
和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。语法:
Box operator+(const Box&); //一个参数
Box operator+(const Box&, const Box&) //两个对象相加
案例:
#include <iostream>
using namespace std;
class Box
{
public:
double getVolume(void)
{
return length * breadth * height;
}
void setLength( double len )
{
length = len;
}
void setBreadth( double bre )
{
breadth = bre;
}
void setHeight( double hei )
{
height = hei;
}
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 程序的主函数
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
Box Box3; // 声明 Box3,类型为 Box
double volume = 0.0; // 把体积存储在该变量中
// Box1 详述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// Box2 详述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// Box1 的体积
volume = Box1.getVolume();
cout << "Volume of Box1 : " << volume <<endl;
// Box2 的体积
volume = Box2.getVolume();
cout << "Volume of Box2 : " << volume <<endl;
// 把两个对象相加,得到 Box3
Box3 = Box1 + Box2;
// Box3 的体积
volume = Box3.getVolume();
cout << "Volume of Box3 : " << volume <<endl;
return 0;
}
可重载的运算符:
- 双目运算符: +、-、*、/、%
- 关系运算符:==、!=、<、>、<=、>=
- 逻辑运算符:||、&&、!
- 单目运算符:+、-、*、&
- 自增自减运算符:++、–
- 位运算符:|、&、~、^、<<、>>
- 赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=
- 空间申请释放:new、delete、new[]、delete[]
- 其它运算符:()、->、,、[]
C++多态
多态:
当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。例如:
#include <iostream>
using namespace std;
class Shape{
protected:
int width, height;
public:
Shape(int a = 0, int b = 0){
width = a;
height = b;
}
virtual int area(){ //虚函数,编译器看的是指针内容,而不是它的类型
return 0;
}
};
class Rectangle: public Shape{
public:
Rectangle(int a = 0, int b = 0): Shape(a, b){}
int area(){
return(width * height);
}
};
class Triangle: public Shape{
public:
Triangle(int a = 0, int b = 0):Shape(a, b){}
int area(){
return (width * height / 2);
}
};
int main(){
Shape *shape; //存储矩形的指针
Rectangle rec(10, 7);
Triangle tri(10, 5);
shape = &rec; //存储矩形的引用
cout << shape->area() << endl; //调用矩形的求面积函数area
shape = &tri; //存储三角形的地址
cout << shape->area() << endl; //调用三角形的面积
return 0;
}
纯虚函数:
在基类中有时候需要定义虚函数,以便在派生类重新定义该函数更好地适用对象,但时在基类中又不能对虚函数给出有意义的实现,这个时候就需要用到纯虚函数。例如:
class Shape{
protected:
int width, height;
public:
Shhape(int a = 0, int b = 0){
width = a;
height = b;
}
virtual int area() = 0; // =0告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
};
C++抽象类
案例:
#include <iostream>
using namespace std;
class Shape
{
public:
virtual int getArea() = 0; //基类中的接口
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
class Rectangle : public Shape
{
public:
int getArea()
{
return (width * height);
}
};
class Triangle : public Shape
{
public:
int getArea()
{
return (width * height) / 2;
}
};
int main(void)
{
Rectangle Rect;
Triangle Tri;
Rect.setWidth(5);
Rect.setHeight(7);
cout << Rect.getArea() << endl;
Tri.setWidth(5);
Tri.setHeight(7);
cout << Tri.getArea() << endl;
return 0;
}
C++文件和流
从文件读取流和向文件写入流。需要使用 C++中另一个标准库fstream
,要在 C++中进行文件处理,必须在 C++源代码文件中包含头文件<iostream>和<fstream>,fstream 库它定义了三个新的数据类型:
数据类型 | 描述 |
---|---|
ofstream | 该数据类型表示输出文件流,用于创建文件并向文件写入信息。 |
ifstream | 该数据类型表示输入文件流,用于从文件读取信息 |
fstream | 该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件、向文件写入信息、从文件读取信息。 |
打开文件:
在从文件读取信息或者向文件写入信息之前,必须先打开文件。ofstream
和fstream
对象都可以用来打开文件进行写操作,如果只需要打开文件进行读操作,则使用 ifstream 对象。
下面是 open()函数的标准语法,open()函数是 fstream、ifstream、ofstream 对象的一个成员。
void open(const char *filename, ios::openmode mode);
open 函数中接收两个参数,第一个参数是指定打开文件的名称和位置,第二个参数定义文件被打开的模式。可使用的模式如下:
|模式标志|描述|
|ios::app|追加模式。所有写入都追加到文件末尾。|
|ios::ate|文件打开后定位到文件末尾。|
|ios::in|打开文件用于读取。|
|ios::out|打开文件用于写入。|
|ios::trunc|如果文件存在,其内容将在打开文件之前被截断,即把文件长度设为 0。|
以上的模式可以把两种以上的模式结合使用。例如:
ofstream outfile;
outfile.open("test.txt", ios::out | ios::trunc); //写入模式打开文件,并希望截断文件,以防文件存在
ifstream afile;
afile.open("test.txt", ios::out | ios::in); //打开文件用于读写
关闭文件:
在 C++程序终止时,它会自动关闭刷新所有流,释放所有分配的内存,并关闭所有打开文件,但是为了保证程序的效率,应该在用完打开文件后关闭文件。close()是 fstream、ifstream、ofstream 对象的一个成员。
void close;
写入文件:
在 C++编程中,我们使用流插入运算符<<
向文件写入信息,就像使用该运算符输出信息到屏幕一样。不同的是,写入信息到文件使用的是 ofstream 或 fstream 对象,而不是 cout 对象。
读取文件:
在 C++编程中,我们使用流提取运算符>>
从文件读取信息,就像使用该运算符从键盘输入信息一样。不同的是,从文件中读取信息使用的是 ifstream 或 fstream 对象,而不是 cin 对象。
读取和写入实例:
#include <fstream>
#include <iostream>
using namespace std;
int main(){
char data[100];
ofstream outfile; //写模式打开文件
outfile.open("./test.txt");
cin.getline(data, 100); //读取一行
outfile << data << endl; //写入文件
cin >> data;
cin.ignore(); //忽略掉之前读语句留下的多余字符
outfile << data << endl; //写入文件
outfile.close(); //关闭写流
ifstream infile; //读模式打开文件
infile.open("./test.txt");
infile >> data; //从文件读取数据
cout << data << endl; //在屏幕上写入数据
infile.close(); //关闭读流
return 0;
}
文件位置指针:
istream 和 ostream 都提供了用于重新定位文件位置指针的成员函数,这些成员函数包括关于 istream 的seekg()
函数和 ostream 的seekp()
函数。seekg 和 seekp 的参数通常是一个长整型,第二个参数可以用于指定查找方向。查找方向可以是 ios::beg(默认的,从流的开头开始定位),也可以是 ios::cur(从流的当前位置开始定位),也可以是 ios::end(从流的末尾开始定位)。案例:
// 定位到 fileObject 的第 n 个字节(假设是 ios::beg)
fileObject.seekg( n );
// 把文件的读指针从 fileObject 当前位置向后移 n 个字节
fileObject.seekg( n, ios::cur );
// 把文件的读指针从 fileObject 末尾往回移 n 个字节
fileObject.seekg( n, ios::end );
// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );
C++异常处理
案例:
#include <iostream>
#include <exception>
using namespace std;
struct MyException : public exception //自定义异常
{
const char * what () const throw () //what() 是异常类提供的一个公共方法,它已被所有子异常类重载。这将返回异常产生的原因。
{
return "C++ Exception";
}
};
int main()
{
try
{
throw MyException(); //抛出自定义的异常
}
catch(MyException& e) //捕获抛出的异常
{
std::cout << e.what() << std::endl; //打印异常信息
}
catch(std::exception& e)
{
//其他的错误
}
throw "throw a exception"; //手动抛异常
}
C++动态内存
C++程序中的内存分为两个部分:
- 栈:在函数内部声明的所有变量都将占用栈内存。
- 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
很多时候,无法提前预知需要多少内存来存储某个定义变量中的特定信息,所需内存的大小需要在运行时才能确定。在 C++程序中,可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new 运算符。
使用 new 运算符来为任意的数据类型动态分配内存的语法:
new data-type;
data-type 可以是包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。
定义一个指向 double 类型的指针,然后请求内存,该内存在执行时被分配。我们可以按照下面的语句使用 new 运算符来完成这点:
double* value = NULL; //初始化为null的指针
value = new double; //为变量请求内存
delete value; //释放内存
如果自由内存区已被用完,可能无法成功分配内存。所以建议检查 new 运算符是否返回 NULL 指针,并采取以下适当的操作:
double* value = NULL;
if(value = new double){
cout << "out of memory" << endl;
exit(1);
}
new 和 delete 运算符案例:
#include <iostream>
using namespace std;
int main(){
double* value = NULL; //初始化为null的指针
value = new double; //为变量请求内存
*value = 99.99; //在分配的地址存储值
cout << "value : " << *value << endl;
delete value;
return 0;
}
数组的动态内存分配:
//---一维数组---
char* value = NULL; //初始化null的指针
value = new char[20]; //分配20个字符的数组
delete [] value; //删除value所指向的数组
int *array = new int[n]; //动态分配,数组长度n
delete [] array; //释放内存
//---二维数组---
int **array
// 假定数组第一维长度为 m, 第二维长度为 n
// 动态分配空间
array = new int *[m];
for( int i=0; i<m; i++ )
{
array[i] = new int [n] ;
}
//释放
for( int i=0; i<m; i++ )
{
delete [] arrary[i];
}
delete [] array;
//---三维数组---
int ***array;
// 假定数组第一维为 m, 第二维为 n, 第三维为h
// 动态分配空间
array = new int **[m];
for( int i=0; i<m; i++ )
{
array[i] = new int *[n];
for( int j=0; j<n; j++ )
{
array[i][j] = new int [h];
}
}
//释放
for( int i=0; i<m; i++ )
{
for( int j=0; j<n; j++ )
{
delete[] array[i][j];
}
delete[] array[i];
}
delete[] array;
对象的动态内存分配:
#include <iostream>
using namespace std;
class Box{
public:
Box(){
cout << "调用构造函数" << endl;
}
~Box(){
cout << "调用析构函数" << endl;
}
};
int main(){
Box* boxArray = new Box[4];
delete [] boxArray;
return 0;
}
如果要为一个包含四个 Box 对象的数组分配内存,构造函数将被调用 4 次,同样地,当删除这些对象时,析构函数也将被调用相同的次数(4 次)。
C++命名空间
命名空间可以作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上命名空间就是定义了一个范围。定义命名空间使用namespace
关键字,语法:
namespace namespace_name{
//代码声明
}
name::code; //调用变量或函数(code)
案例:
#include <iostream>
using namespace std;
namespace first_space{
void func(){
cout << "first_space" << endl;
}
}
namespace second_space{
void func(){
cout << "second_space" << endl;
}
}
int main(){
first_space::func(); //第一个命名空间的函数
second_space::func(); //第一个命名空间的函数
return 0;
}
using指令:
可以使用 using namespace 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。案例:
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func(){
cout << "first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "second_space" << endl;
}
}
using namespace first_space; //指定使用的命名空间
int main ()
{
// 调用第一个命名空间中的函数
func();
return 0;
}
using
指令也可以用来指定命名空间中的特定项目。例如,如果只打算用 std 命名空间中的 cout 部分,可以使用如下语句:using std::cout;
,随后的代码中,在使用 cout 时就可以不用加上命名空间名称作为前缀,但是 std 命名空间中的其他项目仍然需要加上命名空间名称作为前缀,如下所示:
#include <iostream>
using std::cout;
int main ()
{
cout << "std::endl is used with std!" << std::endl;
return 0;
}
嵌套的命名空间案例:
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
}
using namespace first_space::second_space;
int main ()
{
// 调用第二个命名空间中的函数
func();
return 0;
}
C++模板
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。
每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector <int>
或 vector <string>
。函数模板语法:0
template <typename type> re-type func-name(parameter list){
//函数主体
}
函数模板案例:
#include <iostream>
#include <string>
using namespace std;
template <typename T>
inline T const& Max(T const& a, T const& b){
return a < b ? b : a;
}
int main(){
int i = 39;
int j = 20;
cout << "max: " << max(i, j) << endl;
double f1 = 13.5;
double f2 = 20.7;
cout << "max: " << max(f1, f2) << endl;
string s1 = "hello";
string s2 = "world";
cout << "max: " << max(s1, s2) << endl;
return 0;
}
类模板语法:
template <class type> class class-name{
//类的主体
}
类模板案例:
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
using namespace std;
template <class T>
class Stack
{
private:
vector<T> elems; // 元素
public:
void push(T const &); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const
{ // 如果为空则返回真。
return elems.empty();
}
};
template <class T>
void Stack<T>::push(T const &elem)
{
// 追加传入元素的副本
elems.push_back(elem);
}
template <class T>
void Stack<T>::pop()
{
if (elems.empty())
{
throw out_of_range("Stack<>::pop(): empty stack");
}
// 删除最后一个元素
elems.pop_back();
}
template <class T>
T Stack<T>::top() const
{
if (elems.empty())
{
throw out_of_range("Stack<>::top(): empty stack");
}
// 返回最后一个元素的副本
return elems.back();
}
int main()
{
try
{
Stack<int> intStack; // int 类型的栈
Stack<string> stringStack; // string 类型的栈
// 操作 int 类型的栈
intStack.push(7);
cout << intStack.top() << endl;
// 操作 string 类型的栈
stringStack.push("hello");
cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const &ex)
{
cerr << "Exception: " << ex.what() << endl;
return -1;
}
}
C++预处理器
预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。所有的预处理器指令都是以井号#
开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号;结尾。C++ 还支持很多预处理指令,比如 #include、#define、#if、#else、#line 等,让我们一起看看这些重要指令。
#define预处理:
用于创建符号常量。该符号常量通常称为宏
,指令的一般形式是:
#define macro-name replacement-text
当这一行代码出现在一个文件中时,在该文件中后续出现的所有宏都将会在程序编译之前被替换为 replacement-text。例如:
#include <iostream>
using namespace std;
#define PI 3.14159
int main ()
{
cout << "Value of PI :" << PI << endl;
return 0;
}
参数宏:
可以使用 #define 来定义一个带有参数的宏,如下所示:
#include <iostream>
using namespace std;
#define MIN(a, b) (a < b ? a : b)
int main(){
int i, j;
i = 100;
j = 30;
cout << "min: " << MIN(i, j) << endl;
return 0;
}
#和##运算符:
# 字符串化的意思,出现在宏定义中的#是把跟在后面的参数转换成一个字符串。## 连接符号,把参数连在一起。可以使用 #define 来定义一个带有参数的宏,如下所示:
#include <iostream>
using namespace std;
#define MKSTR(x) #x
int main(){
cout << MKSTR(hello c++) << endl;
//cout << "hello c++" << endl; //上面的语句被替换成这行
return 0;
}
//编译或者执行时输出:hello c++
两个##案例:
#include <iostream>
using namespace std;
#define concat(a, b) a ## b
int main()
{
int xy = 100;
cout << concat(x, y); //被替换为 cout << xy;
return 0;
}
C++中的预定义宏:
#include <iostream>
using namespace std;
int main ()
{
cout << "Value of __LINE__ : " << __LINE__ << endl; //编译时包含当前行号
cout << "Value of __FILE__ : " << __FILE__ << endl; //编译时包含当前文件名
cout << "Value of __DATE__ : " << __DATE__ << endl; //包含一个形式为 month/day/year 的字符串日期
cout << "Value of __TIME__ : " << __TIME__ << endl; //包含一个形式为 hour:minute:second 的字符串,它表示程序被编译的时间。
return 0;
}