一、C++基础
1.cout输出流
1.cout是一个输出流对象,他是 console out(控制台输出)的缩写。是属于basic_ostream类的对象。ostream类在iostream头文件中定义。
#include<iostream> //头文件 输入输出流
using namespace std; //命名空间
int addArray(int *array, int n);
int main(){
int data[]={0,1,2,3,4,5,6,7,8,9};
int size = sizeof(data)/sizeof(data[0]);
cout << "结果是" << addArray(data,size) << endl; //<<重载
return 0;
}
int addArray(int *array,int n){ //指针指向array的地址
int sum=0;
int i=0;
for(i=0; i<n;i++){
sum += *array++;
}
return sum;
}
cout.precision(val):在输出的时候,设定输出值以新的浮点数精度值显示,即小数点后保留val位 。设置精度
precision():返回当前的浮点数的精度值
cout.precision(3);
cout.width():一个带参数的 一个不带参数的
streamsize width( ) const;
streamsize width(streamsize _Wide);
cout.width( 20 ); //将宽度设置为20,默认右对齐
cout << cout.width( ) << endl;
cout << cout.width( ) << endl;
第一个函数是获取当前的输出宽度;
第二个函数是设置指定的输出宽度,该宽度值必须是有效的:大于0
函数的返回值:当前输出宽度
a、控制符int width()将用来调整字段的宽度,因为width是成员函数,所以要通过对象来调用,比如cout.width()将显示当前的字段宽度,默认为0,而cout.width(3)将把字段宽度设定为3。
注意C++容纳字段的方式为给字段分配刚好合适的宽度来容纳字段,所以C++中默认的字段宽度为0,以适合于所有的字段。
b、width的默认对齐方式为右对齐,即如果cout.width(12)如果字段没有这么宽,则将在字段的左边填以空格来达到12个字段的宽度。
c、还要注意的是width只影响他设置后的下一个输出,再下一个字段输出后,后继的字段被恢复为默认值,比如cout.width(12); cout<<2<<3;则输出2时会以12字段的宽度显示,但显示3时就会以默认的方式显示了。
d、int width()调用他时将会反回上一次的字段宽度的值。
2.cin输入流
流对象cin:这个对象类型是istream,他知道如何从用户终端读取数据。
cin>>i :cin输入操作符又称为提取操作符,他一次从输入流对象cin提取一个整数。
当用户进行键盘输入时,对应的字符将输入到操作系统的键盘缓冲区中,然后将内容传输到cin流的内部缓冲区。
int main(){
int sum = 0;
cout <<"输入"; //22 11 22 33
int i;
while(cin >> i){ //重载,从cin提取数据给i 如果到达了文件尾或者提取操作符遇到一个非法值,返回false
sum+=i;
while(cin.peek()==' '){
cin.get();
}
if(cin.peek()=='/n'){
break;
}
}
cout << "result:" << sum << endl
}
cin的一些方法:
-
cin.ignore(i):忽略前i个字符
-
cin.getline(buf,10):获取10个字符给buf
-
cin.peek()!="/n":挑取一个字符,是否不等于回车
-
cin.get():获取一个字符
-
cin.read(buf,20):读取20个字符,到buf缓冲区中
-
cin.gcount():计算提取到了多少字符
-
cin.write(buf,20):从缓冲区输出20个字符
3.文件I/O
3.1文件读取类 ifream
#include<fstream>
#include<iostream>
using namespace std;
int main(){
ifstream in; //创建一个ifstream对象in
in.open("test.txt"); //打开文件
if(!in){ //返回为0
cerr <<"文件打开失败"<<endl;
return 0;
}
char x;
while(in >> x){ //将in重载流到了x
cout << x; x流到cout输出终端 即屏幕上
}
cout << endl;
in.close();
return 0;
}
ifstream in(char* filename,int open_mode) filename表示文件的名称,他是一个字符串,open_mode表示打开模式,用来定义用怎么样的方式打开文件。
ios::in 打开一个可读取文件
ios::out 打开一个可写入文件
ios::binary 以二进制的形式打开一个文件
ios::app 写入所有的数据将被追加到文件的末尾
ios::trunk 删除文件原来已经存在的内容
ios::nocreate 如果打开的文件并不存在,那么open函数无法运行
ios::noreplece 如果打开的文件已存在,试图用open函数打开时将返回一个错误。
3.2文件写入类ofream
#include<fstream>
#include<iostream>
using namespace std;
int main(){
ofstream out; //创建一个ifstream对象in ofstream out("test.txt",ios::open)
ofstream out ("text.txt",ios::in | ios::out)
out.open("test.txt"); //打开文件
if(!out){ //返回为0
cerr <<"文件打开失败"<<endl;
return 0;
}
for(int i =0;i<10;i++){
out<<i;
}
out << endl;
out.close();
return 0;
}
4.函数的重载
用同样的名字在定义一个有着不同参数但有着同样用途的函数。
使用不同的参数对函数进行重载
5.复杂的数据类型
5.1数组
C++ 支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 numbers[0]、numbers[1]、...、numbers[99] 来代表一个个单独的变量。数组中的特定元素可以通过索引访问。
所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。
1.声明数组
type name[size]
2.初始化数组
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
3.访问数组元素
double salary = balance[9];
4.定义字符串
std::String str;
5.2指针
1.取址:每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。
cout << &var1 << endl;
对齐:区块在文件和内存中按照一定的规律来对其
变量的地址在程序执行期间是不会发生变化的。不过,同一个程序不同时间加载到内存中,同一个变量的地址是会改变的。
2.指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。
3.指针变量声明的一般形式为:
type* var-name;
指针是专门用来存放地址的特殊类型变量。指针变量存放的是地址。
在这里,type 是指针的基类型,它必须是一个有效的 C++ 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * ,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:
int* ip; /* 一个整型的指针 */
double* dp; /* 一个 double 型的指针 */
float* fp; /* 一个浮点型的指针 */
char* ch; /* 一个字符型的指针 */
4.解引用:利用指针改变值:在指针名前面加一个*号
int a = 123;
int* apointer = &a;
std::cout << *apointer
把整数变量a的地址存放在apointer指针里之后, *apointer和变量a将代表同一个值
因此*apointer=123会改变a的值
5.注意
星号有两种用途:1.创建指针 2.解引用
c++允许多个指针有同样的值
int* p1 = &my;
int* p2 = &my;
c++支持无类型指针,就是没有被声明为某种特定类型的指针
void* p;
对于无类型指针进行解引用前,必须先把他转换为一种适当的数据类型
#include <iostream>
using namespace std;
int main ()
{
int var = 20; // 实际变量的声明
int *ip; // 指针变量的声明
ip = &var; // 在指针变量中存储 var 的地址
cout << "Value of var variable: ";
cout << var << endl; //20
// 输出在指针变量中存储的地址
cout << "Address stored in ip variable: ";
cout << ip << endl; //var的地址
// 访问指针中地址的值
cout << "Value of *ip variable: ";
cout << *ip << endl; //20
return 0;
}
6.数组和指针
数组的名字其实也是一个指向其第一个元素的指针
int* ptr1 = &myArray[0];
int* ptr2 = myArray;
int Array[5] = {1,2,3,4,5};
int* ptr = Array;
则:
*ptr+1; 值加1
*(ptr+1) 下一个地址
5.3结构
1.**结构***是 C++ 中另一种用户自定义的可用的数据类型,它允许存储不同类型的数据项。
struct name
{
type varName1;
type varName2;
} object_names;
比如:
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
2.访问结构成员:
为了访问结构的成员,使用成员访问运算符(.)。成员访问运算符是结构变量名称和我们要访问的结构成员之间的一个句号。
#include <iostream>
#include <cstring>
using namespace std;
// 声明一个结构体类型 Books
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
Books Book1; // 定义结构体类型 Books 的变量 Book1
// Book1 详述
strcpy( Book1.title, "C++ 教程");
strcpy( Book1.author, "Runoob");
strcpy( Book1.subject, "编程语言");
Book1.book_id = 12345;
// 输出 Book1 信息
cout << "第一本书标题 : " << Book1.title <<endl;
cout << "第一本书作者 : " << Book1.author <<endl;
cout << "第一本书类目 : " << Book1.subject <<endl;
cout << "第一本书 ID : " << Book1.book_id <<endl;
return 0;
}
3.结构作为函数参数 可以把结构作为函数参数,传参方式与其他类型的变量或指针类似
#include <iostream>
#include <cstring>
using namespace std;
void printBook( struct Books book );
// 声明一个结构体类型 Books
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
Books Book1; // 定义结构体类型 Books 的变量 Book1
Books Book2; // 定义结构体类型 Books 的变量 Book2
// Book1 详述
strcpy( Book1.title, "C++ 教程");
strcpy( Book1.author, "Runoob");
strcpy( Book1.subject, "编程语言");
Book1.book_id = 12345;
// Book2 详述
strcpy( Book2.title, "CSS 教程");
strcpy( Book2.author, "Runoob");
strcpy( Book2.subject, "前端技术");
Book2.book_id = 12346;
// 输出 Book1 信息
printBook( Book1 );
// 输出 Book2 信息
printBook( Book2 );
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;
}
4.指向结构的指针
定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:
struct Books *struct_pointer;
在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:
struct_pointer = &Book1;
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 Book1; // 定义结构体类型 Books 的变量 Book1
Books Book2; // 定义结构体类型 Books 的变量 Book2
// Book1 详述
strcpy( Book1.title, "C++ 教程");
strcpy( Book1.author, "Runoob");
strcpy( Book1.subject, "编程语言");
Book1.book_id = 12345;
// Book2 详述
strcpy( Book2.title, "CSS 教程");
strcpy( Book2.author, "Runoob");
strcpy( Book2.subject, "前端技术");
Book2.book_id = 12346;
// 通过传 Book1 的地址来输出 Book1 信息
printBook( &Book1 );
// 通过传 Book2 的地址来输出 Book2 信息
printBook( &Book2 );
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;
}
5.4传值、传址、传引用
1.值传递:在默认情况下,参数只能以值传递的方式给函数
2.址传递:如果传递过去的是地址,在函数中必须要通过*对指针进行解引用。
chageAge(&age,age+1);
void chageAge(int* age,int newAge)
{
*age = newAge;
std::cout << *age << "\n";
}
3.引用:引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
C++ 引用 vs 指针
-
不存在空引用。引用必须连接到一块合法的内存。
-
只能指向一个对象:一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
-
引用必须在创建时被初始化。指针可以在任何时间被初始化。
4.创建引用:
int i = 17;
int& r = i; //&为引用 r是一个初始化为i的整型引用
double& s = d; //s是一个初始化为d的double引用
#include <iostream>
using namespace std;
int main ()
{
// 声明简单的变量
int i;
double d;
// 声明引用变量
int& r = i;
double& s = d;
i = 5;
cout << "Value of i : " << i << endl;
cout << "Value of i reference : " << r << endl;
d = 11.7;
cout << "Value of d : " << d << endl;
cout << "Value of d reference : " << s << endl;
return 0;
}
5.引用传递:
void changeAge(int &x ,int &y); //传一个整型变量的地址
main(){
changeAge(x,y); //自动获取地址
}
5.5 联合、枚举和数据别名
1.联合(union):每次只能存储这些值中的某一个,会覆盖之前的数据
union minm{
unsigned long birthday;
unsigned short ssn;
char* pet;
};
2.枚举(enum):创建一个可取值列表
enum color { red, green, blue } c;
c = blue;
例如,下面的代码定义了一个颜色枚举,变量 c 的类型为 color。最后,c 被赋值为 "blue"。
默认情况下,第一个名称的值为 0,第二个名称的值为 1,第三个名称的值为 2,以此类推。但是,您也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。例如,在下面的枚举中,green 的值为 5。
enum color { red, green=5, blue };
在这里,blue 的值为 6,因为默认情况下,每个名称都会比它前面一个名称大 1,但 red 的值依然为 0。
3.类型别名(Typedef)可以为一个类型定义创建一个别名
Typedef int* intpointer;
intpointer myintpointer;
6.对象和类
6.1定义类
定义一个类需要使用关键字 class,然后指定类的名称,并类的主体是包含在一对花括号中,主体包含类的成员变量和成员函数。
定义一个类,本质上是定义一个数据类型的蓝图,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。
class Box
{
public:
double length; // 盒子的长度 属性
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
关键字 public 确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可访问的。您也可以指定类的成员为 private 或 protected。
6.2定义对象
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
C++可以在类里声明常量,但是不允许对他进行赋值
class Box
{
public:
double length=82; // 盒子的长度 属性
};
可以创建静态常量
class Box
{
public:
static const double length=85; // 盒子的长度 属性
};
类似于结构,可以在某个声明类的同事创建该类的对象
class Box
{
public:
static const double length=85; // 盒子的长度 属性
}box1;
#include <iostream>
using namespace std;
class Box //定义一个类
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
// 成员函数声明
double get(void);
void set( double len, double bre, double hei );
};
// 成员函数定义
double Box::get(void) //box类的get方法
{
return length * breadth * height;
}
void Box::set( double len, double bre, double hei) //box类的set方法
{
length = len;
breadth = bre;
height = hei;
}
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
Box Box3; // 声明 Box3,类型为 Box
double volume = 0.0; // 用于存储体积
// box 1 详述
Box1.height = 5.0;
Box1.length = 6.0;
Box1.breadth = 7.0;
// box 2 详述
Box2.height = 10.0;
Box2.length = 12.0;
Box2.breadth = 13.0;
// box 1 的体积
volume = Box1.height * Box1.length * Box1.breadth;
cout << "Box1 的体积:" << volume <<endl;
// box 2 的体积
volume = Box2.height * Box2.length * Box2.breadth;
cout << "Box2 的体积:" << volume <<endl;
// box 3 详述
Box3.set(16.0, 8.0, 12.0);
volume = Box3.get();
cout << "Box3 的体积:" << volume <<endl;
return 0;
}
6.3 定义构造器
1.类似于java的有参和无参构造
-
方法名和类名相同
-
系统第一时间会调用这个类的构造器
-
构造器没有返回值
class Car{
Car(void);
}
默认会有 进行初始化
2.继承机制的构造器
Pig::Pig(std::string theName) : Animal(theName){
}
当调用Pig()构造器时(以theName作为输入参数),Animal()构造器也将被调用(theName输入参数将传递给他)
6.4定义析构器
1.在创建对象时,系统会自动调用一个特殊的方法,即构造器。
相应的,在销毁一个对象时,系统也应该会调用另一个特殊方法达到对应效果,即构造器。
构造器用来完成初始化和准备工作(申请内存和分配),析构器用来完成事后所必须的清理操作(清理内存)
~ClassName();
先进行子类析构器,再进行基类构造器
6.5this指针和类的继承
继承的语法:
// 基类(java的父类)
class Animal {
// eat() 函数
// sleep() 函数
};
//派生类(java的基类)
class Dog : public Animal {
// bark() 函数
};
class derived-class(派生类): access-specifier base-class(基类)
其中,访问修饰符 access-specifier 是 public、protected 或 private 其中的一个,base-class 是之前定义过的某个类的名称。
如果未使用访问修饰符 access-specifier,则默认为 private。
派生类可以访问基类中所有的非私有成员。即可以访问public和protected。
-
公有继承(public): 当一个类派生自公有 基类时,基类的公有 成员也是派生类的公有 成员,基类的保护 成员也是派生类的保护 成员,基类的私有 成员不能直接被派生类访问,但是可以通过调用基类的公有 和保护成员来访问。
-
保护继承(protected): 当一个类派生自保护 基类时,基类的公有 和保护 成员将成为派生类的保护成员。
-
私有继承(private): 当一个类派生自私有 基类时,基类的公有 和保护 成员将成为派生类的私有成员
7.C++重载运算符和重载函数
重载:方法名相同,实际的参数列表和定义不相同。
当调用一个** 重载函数或 重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策**
7.1C++中的函数重载
#include <iostream>
using namespace std;
class printData
{
public:
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
void print(char c[]) {
cout << "字符串为: " << c << endl;
}
};
int main(void)
{
printData pd;
// 输出整数
pd.print(5);
// 输出浮点数
pd.print(500.263);
// 输出字符串
char c[] = "Hello C++";
pd.print(c);
return 0;
}
7.2C++ 中的运算符重载
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
Box operator+(const Box&);
函数类型 operator 运算符名称(形参表列)
声明加法运算符 用于把两个 Box 对象相加,返回最终的 Box 对象。大多数的重载运算符可被定义为普通的非成员函数 或者被定义为类成员函数 。如果我们定义上面的函数为类的非成员函数,那么我们需要为每次操作传递两个参数,如下所示:
Box operator+(const Box&, const Box&);
不允许被重载的运算符:. .* :: sizeof ?:
重载不允许改变运算符运算对象(操作数)个数
重载不能改变运算符的优先级别
a + b 相当于 a 对象调用+方法并且传入参数时 b 对象。
类内重载
#include <iostream>
using namespace std;
class Point{
public:
Point(){};
Point (int x, int y): x(x),y(y) {};
Point operator+(const Point &b){ //类内重载,运算符重载函数作为类的成员函数
Point ret;
ret.x = this->x + b.x;
ret.y = this->y + b.y;
return ret;
}
int x,y;
};
int main() {
Point a(2,4),b(5,3);
Point c = a + b; //这里c++编译器会,自动去找 + 运算符的重载函数
cout<< "x :" << c.x << endl;
cout<<"y :" << c.y << endl;
}
类外重载:用友元函数实现
#include <iostream>
using namespace std;
class Point{
public:
Point(){};
Point (int x, int y): x(x),y(y) {};
friend Point operator+(const Point &, const Point &); //声明类的友元函数
int x,y;
};
Point operator+(const Point &a,const Point &b){//类外重载,运算符重载函数作为类的友元函数
Point ret;
ret.x = a.x + b.x;
ret.y = a.y + b.y;
return ret;
}
int main() {
Point a(2,4),b(5,3);
Point c = a + b;
cout<< "x :" << c.x << endl;
cout<<"y :" << c.y << endl;
}
重载<<操作符
#include <iostream>
using namespace std;
class Point{
public:
Point(){};
Point (int x, int y): x(x),y(y) {};
friend ostream &operator<<(ostream &out , const Point &a); //因为 << 是C++提供的类,我们无法访问,只能用友元函数。
private:
int x,y;
};
//<< 运算符重载的函数实现 ostream是输入输出流的类
ostream &operator<<(ostream &out , const Point &a){
out << "<Point>( " << a.x << ", " << a.y << ")";
return out;
}
int main() {
cout << c<< endl; //直接输出类会报错,需要上面的 << 运算符重载
}
ostream &operator<<(ostream &out , const Point &a)函数,首先在重载<<时,返回值类型是ostream&, 第一个参数也是ostream& 。也就是说,表达式cout<<c的返回值仍是 cout,所以cout<<c<<endl;才能成立
8.友元关系
友元关系是不同类之间的一种特殊关系,这种关系不仅允许友元访问对方的public方法和属性,还允许友元访问对方的protected和private方法和属性。
声明友元关系
friend class 类名
class Lovers
{
public:
void ask();
protected:
std::string name;
friend class Others; //这样 Others就可以访问到 Lovers这个类的name了
}
9.静态属性和静态方法
创建一个静态属性和静态方法:
只需要在他的声明前加上static保留字即可。
class Pet
{
public:
Pet(std::string theName);
~Pet();
static int getCount(); //静态方法
protected:
static int count; //静态属性
private:
static int count;
};
静态成员是所有对象共享的,所以不能在静态方法里访问非静态的元素。
非静态方法可以访问类的静态成员,也可以访问类的非静态成员。
静态方法可以通过类名调用 persion::fn()
定义了静态属性,就会在其他方法中共用,
10.虚方法
可以直接创建一个指针并让他指向新分配的内存块
int *pointer = new int;
*pointer = 110;
std::cout << *pointer;
delete pointer;
new和delete必须成对出现,分类内存后删除内存
当我们使用基类的引用或指针调用基类中定义的某个函数时,我们并不知道该函数真正的对象是什么类型(属于哪个类),因为它可能是一个基类的对象,也可能是一个子类的对象。
声明虚方法
virtual void play()
虚方法是继承的,一旦在基类中把某个方法声明为虚方法,在子类中就不能把他在声明为虚方法了。
虚方法就是可以让多态可以实现。
#include <iostream>
using namespace std;
class Base
{
public:
Base(int _x):x(_x)
{
}
virtual int get() //在基类上定义虚方法,定义了虚方法之中,就会调用派生类中的方法了
{
cout<<"Base的get"<<endl;
return x;
}
private:
int x;
};
class Derived:public Base
{
public:
Derived(int _y):Base(_y),y(_y)
{
}
int get()
{
cout<<"Derived的get"<<endl;
return y;
}
private:
int y;
};
int main()
{
Base *a=new Derived(5);
cout<<a->get()<<endl;
return 0;
}
若不调用虚方法,编译器在编译的时候a是Base类的指针,所以编译器就认为a指针调用的方法是Base类里的方法,因为这样的执行效率是最快的。
对虚函数调用运行时才被解析,对非虚函数调用时编译时就被解析了
11.抽象类和抽象方法 :纯虚函数
纯虚函数规范了派生类必须重写
该方法对于不同的子类有截然不同的实现,而在基类中我们只需要一个空实现就可以了。
抽象方法就是只有声明而没有实现的方法,一般抽象方法都是基类中的方法,并且与虚方法同时存在,语法如下:
virtual void test() = 0;
在函数声明的末尾加上" = 0" 就表示这是一个抽象方法,告诉编译器不要在这个类中去找它的实现。 而具体的实现在子类中的覆盖方法内。
如果类中至少由一个函数被声明为纯虚函数,则这个类是抽象类。
定义一个函数为虚函数,不代表函数为不被实现的函数。定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
12.多态性
编译时的多态性:通过重载实现。
运行时的多态性:通过虚函数实现。
去完成某个行为,当不同的对象去完成时会**产生出不同的状态**
多态的构成条件:
-
必须通过基类的指针或者引用调用虚函数。
-
被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。
13.多继承
语法:
class TeacheringStudent:public Student,public Teacher
不建议多继承,当不同父类之间出现同名成员时,需要加作用域区分。
13.1.虚继承
主要解决菱形继承的问题
语法:
class Teacher:virtual public Persion{
}
在C++中,**为解决二义性问题,**引入虚继承机制。
注意:虚继承是在多继承中讲的,而虚函数是在多态中讲的
当菱形继承,两个父类拥有相同的数据,需要加作用域区分
class Annmal{
public:
m_Age;
};
class sheep:public Annmal{};
class Tuo :public Annmal{};
class sheepTuo : public sheep,public Tuo{};
int main(){
sheepTuo st;
st.m_Age=18; //作用域不明确
st.sheep::m_Age=18; //加作用域
}
这个数据只要一份就可以,菱形继承导致数据两份,造成资源的浪费:用虚继承解决
class Annmal{
public:
m_Age;
};
class sheep:vistual public Annmal{};
class Tuo :vistual public Annmal{};
class sheepTuo : public sheep,public Tuo{};
int main(){
sheepTuo st;
st.sheep::m_Age=18; //加作用域
st.Tuo::m_Age=28;
//最终的结果就是28 结果只有一个了
}
虚继承的特性
需虚基类指针: 虚继承使得公共的基类在派生类中只有一份,虚继承在多重继承的基础上多了vtable表(虚表)来存储到公共基类的偏移,而在使用公共基类时,通过vptr指针(虚指针)查表得到,该虚指针和虚表的维护由系统来进行。
派生类维护了一个指针,指向同一个地址。
14.数据抽象
数据抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。
数据抽象有两个重要的优势:
-
类的内部受到保护,不会因无意的用户级错误导致对象状态受损。
-
类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告。
#include <iostream>
using namespace std;
class Adder{
public:
// 构造函数
Adder(int i = 0)
{
total = i;
}
// 对外的接口
void addNum(int number)
{
total += number;
}
// 对外的接口
int getTotal()
{
return total;
};
private:
// 对外隐藏的数据
int total;
};
int main( )
{
Adder a;
a.addNum(10);
a.addNum(20);
a.addNum(30);
cout << "Total " << a.getTotal() <<endl;
return 0;
}
15.数据封装
数据封装(Data Encapsulation)是面向对象编程(OOP)的一个基本概念,它通过将数据和操作数据的函数封装在一个类中来实现。这种封装确保了数据的私有性和完整性,防止了外部代码对其直接访问和修改。
数据封装 是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。
public proteced privite
16.assert函数
专门为调试而准备的工具函数
#include<cassert>
assert()函数只需要一个参数,他将测试这个输入参数的真 or 假状态
try catch throw
在构造器和析构器不应该使用异常。
使用对象作为异常,以值传递的方式抛出异常,以引用传递方式捕获对象。
17.动态内存管理
静态内存:在程序编译时被设置为一个固定的值,类似变量(包括指针变量)、固定数组长度。。。
动态内存:由一些没有名字,只有地址的内存块构成,那些内存块是在程序运行期间动态分配的。
内存池 :从内存池申请内存需要new语句,他将根据你提供的数据类型分配一块大小适当的内存。new语句返回新分配地址块的起始地址。
如果没有足够的内存空间,将抛出std::bad_alloc异常
使用delete语句释放语句。
int *i=new int;
delete i;
i=null
18.动态数组
int a[20];
int *x=a;
指针变量x指向数组a的地址,a[0]和*x都代表数组的第一个元素。
因此
a[1]等价于*(x+1)
创建一个动态数组
int count =10;
int *x=new int[count];
删除动态数组
delete [] x;
19.指针函数和函数指针
函数指针:指向函数首地址的指针变量称为函数指针。
指针函数:一个函数可以带回一个整形数据的值,字符类型值和实型类型的值,还可以带回指针类型的数据,使其指向某个地址单元。
#include<iostream>
int *newInt(int value);
int main(){
int *x = newInt(20); 指针指向地址。
std::cout << *x;
delete x;
x=null;
return 0;
}
int *newInt(int value){
int *myInt = new int;
*myInt = value;
return myInt; 返回一个地址
}
利用指针在某个函数内部改变另一个函数的局部变量的值
传地址过去,然后指针接
swap(&a,&a);
void swap(int *x,int *y){
...
}
任何一个函数都不应该把自己的局部变量的指针作为他的返回值,因为局部变量在栈里,函数结束后会自动释放。
副本构造器
20.高级强制类型转换
静态对象强制类型转换
动态对象强制类型转换
强制类型转换操作符
dynamic_cast<MyClass*>(value)
用来把一种类型的对象指针安全地强制转换为另一种类型的对象指针。
如果value的类型不是一个MyClass类(或MyClass的子类)的指针,这个操作符将返回null
Company *company = new Company("APPLE","Iphone");
TechCompany *techCompany = dynamic_cast<TechCompany *>(company);
两个尖括号之间写出想要的指针类型,然后是将被转换的值写在括号中。
不同对象之间不可以强制类型转换,得继承之间的对象。
避免内存泄漏
21.命名空间和模块化编程
21.1模块化编程
把程序划分为多个组成部分(模块)这是通过把程序代码分散到多个文件里,等编译程序时再把这些文件重新组合在一起实现的。
系统头文件:保证C++代码的可移植性。用<>括起来
自定义头文件:用双引号括起来
.h文件
头文件用来保存函数的声明,用户自定义类型数据(结构和类)。模板和全局性的常量。
如果一个程序需要多次调用一个或一组函数,或有一个或一组函数需要在多个程序里调用,就应该把他们的声明拿出来放到一个头文件里
头文件只包含必要的代码,比如只声明一个类或只包含一组彼此相关的函数。
问题:多个cpp文件都包含了.h文件,.h文件被声明了两次,着显然没有必要。
C++预处理器
预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。
所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。
我们已经看到,之前所有的实例中都有 #include 指令。这个宏用于把头文件包含到源文件中。
C++ 还支持很多预处理指令,比如 #include、#define、#if、#else、#line 等
1.#define 预处理
#define PI 3.14159
2.参数宏
#define MIN(a,b) (a<b ? a : b)
让头文件只在一个类没有声明的情况下声明,声明过了就不用再声明了
3.预处理器指令-条件编译
#if
#else
#elif 相当于else if
#endif 一个条件指令的结束
#ifdef 如果本指令所引用的定义已存在,执行代码
#ifndef 如果本指令所引用的定义已存在,执行代码
#ifndef RATIONAL_H
#define RATIONAL_H
class Rational{
}
#endif
避免重复定义
当第一次被编译时,RATIONAL_H没有被定义,然后定义他 #define RATIONAL_H,并执行下面的代码,知道endif
当再被编译时,已经被编译了,就不会被执行了。这就是一宏条件 编译条件。
21.2命名空间
namespace
namesapace myNamespace{
}
使用命名空间
std::cout
using 指令
using namespace std;
using std::cout;
22.链接和作用域
1.链接:
内连接:内部连接可以在多个.cpp中定义,互不影响 有的内部连接可以用extern在其他源文件中访问,有的不能
static int a = 1; // static变量
const int b = 1; // 常量
enum Coolean {NO, YES}; // 枚举定义
static void foo() // static 函数
{
}
inline void goo() {} // inline函数
constexpr int soo() {return 1;} // constexpr函数本身就是inline的
class Point //类的定义
{
public:
Point() {};
~Point() {};
}
inline Point::aInline() {}; //类的内连等满足上述条件的类的成员函数
外连接:外部连接不能在多个.cpp中定义,报编译错误
-
外部连接的定义,定义在cpp文件,extern的声明在h文件,这样可以方便其它文件include。
int c = 0; // c.cpp文件 extern int c; // c.h 文件 #include "c.h" // 其它用到c的文件
作用域:局部变量,全局变量
23.模板
23.1函数模板
template <typename type> ret-type func-name(parameter list)
{
// 函数的主体
}
template <typename T>
void swap(T &a,T &b)
{
....
}
调用
swap<int>(i1,i2)
如果某个函数对所有的数据类型都将进行同样的处理,就应该把他编写为一个模板。
如果某个函数对不同的数据类型都进行不同样的处理,就应该把他进行重载。
23.2类模板
template <class T>
class MyClass
{
MyClass();
void swap(T &a, T &b);
};
构造器实现应该是下面这样的
Myclass<T>::Myclass()
{
//初始化操作
}
#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;
}
}
24.向量和迭代器
24.1容器
能容纳两个或者更多的值的数据结构叫做容器
其中,数组是容器
但是数组的长度有限,因此有了向量 vector
向量可以动态的随着往里面添加的元素而无线增大
通过size()方法查长度
用push_back()方法往里面添加东西
用访问数组元素的语法来访问某给定向量里的各个元素4
#include<vector>
int main(){
std::vertor<std::string> names;
names.push_back("zxm");
names.push_back("zxx");
for(int i =0;i<names.size();i++){
std::cout << name[i] <<"\n";
}
return 0;
}
24.2迭代器
向量需要通过下标来变量里面的内容
然后就引出了迭代器,迭代器iterator
#include<vector>
int main(){
std::vertor<std::string> names;
names.push_back("zxm");
names.push_back("zxx");
std::vertor<std::string>::iterator iter = names.begin(); //指向起始位置
while(iter != names.end()){ //最终位置
std::cout << *iter << "\n";
++iter;
}
return 0;
}
迭代器内部其实是一个指针
Rephrase with Ginger (Ctrl+Alt+E)