参考文章:
小甲鱼C++快速入门学习笔记_亦我飞也的博客-CSDN博客_小甲鱼c++
1. 面向对象的思想
1. 封装
封装意味着把对象的属性和方法结合成一个独立的单元。
封装是面向对象思想描述的基础,从此程序员面对的就不再是许多复杂的函数和过程实现,而是少数具有行为能力的个体实例。
2. 抽象
抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节。
3. 继承
子类对象拥有与其基类相同的全部属性和方法,称为继承。
4. 多态
多态是指在基类中定义的行为被子类继承后,可以具有不同的行为表现。
2. 输入输出流操作
1. 从控制台的输入输出
头文件<iostream> , 命名空间std
1. 从控制台输出:cout类
cout << “ Hello World” << endl;
endl为"\n" + 刷新数据缓冲到显示设备;
2. 从控制台输入:cin类
cin 从控制台输入缓冲区中获取数据
cin 读取数据是从第一个非空白字符开始到下一个空白字符结束
(cin>>sth 根据sth的变量类型读取数据,这里变量类型可以为int,float,char,char*,string等诸多类型。这一输入操作,在遇到结束符(Space、Tab、Enter)就结束,且对于结束符,并不保存到变量中。注意:最后一个enter也在缓冲区。)
例如:输入“ I love" ; cin << str; 则str为“I”
返回:成功返回cin本身
到达了文件尾或者提取操作符遇到一个非法值,返回0
常见使用:
cin << str1 << str2;
while(cin >> i) 连续获取数据
操作方法:
cin.ignore(a, ch) :它表示从输入流 cin 中提取字符,提取的字符被忽略,不被使用。
而每抛弃一个字符,它都要进行计数和比较字符:如果计数值达到 a 或者被抛弃的字符是 ch ,
则cin.ignore() 函数执行终止;否则,它继续等待。例如:ignore(7);忽略7个字符
cin.get(字符数组名,接收长度,结束符);获取输入的字符
字符数组名:需要是char的类型数组;不能是string的类
接收长度:实际最大接收的数据为接收长度-1;末尾会添加'\0';
结束符:默认为enter
获取单个字符get(char& c)或者p = cin.get()
cin.getline() 其和cin.get()函数操作类似
但是cin.getline()当输入超长时,会引起cin函数的错误(返回0),后面的cin操作将不再执行。
cin.peek() 查看下一个字符
cin.peek()的返回值是一个int型,如果没有了字符则函数返回值是EOF(-1)
cin.peek() != '0'
cin.gcount() 获取上一次cin读取的字节数;
cin.sync();//清理缓冲区内容
cin.read( buf, 20 ) 直接调用read函数读取
getline() 该函数不是cin的方法;接收一个字符串,可以接收空格并输出;
例如:getline(cin,str), str为string类;
2. 从文件的输入输出
头文件<fstream>,命名空间std
操作文件的三个类:
1. ofstream: 写操作
2. ifstream: 读操作
3. fstream: 读写操作
2.1 文件流的定义
ofstream file;
ifstream file;
fstream file;
2.2 文件流的操作
操作方法
open() 打开文件,也可以定义类的时候直接打开文件ofstream file(char *filename, int open_mode);
下面给出几种常见的打开模式:
ios也在std的命令空间下;
ios::in -- 打开一个可读取文件
ios::out -- 打开一个可写入文件
ios::binary -- 以二进制的形式打开一个文件。
ios::app -- 写入的所有数据将被追加到文件的末尾
ios::trunc -- 删除文件原来已存在的内容
ios::nocreate -- 如果要打开的文件并不存在,那么以此参数调用open 函数将无法进行。 ios::noreplace -- 如果要打开的文件已存在,试图用open 函数打开时将返回一个错误。
int file_write_test(char *message)
{
std::ofstream fs_writer;
fs_writer.open("./file.1.txt", std::ios::out);
if(!fs_writer)
{
std::cout << "open failed;\n" << std::endl;
return -1;
}
fs_writer<< message << std::endl;
fs_writer.close();
return 0;
}
close() 关闭文件
读写操作:
fstream —文件读写操作
常用以下4总方式读取文件:
//第一种
char buf[1024] = { 0 };
while (ifs >> buf)
{
cout << buf << endl;
}
//第二种
char buf[1024] = { 0 };
while (ifs.getline(buf, sizeof(buf)))
{
cout << buf << endl;
}
//第三种
string buf;
while ( getline(ifs, buf))
{
cout << buf << endl;
}
//第四种
char c;
while ((c = ifs.get()) != EOF)
{
cout << c;
}
二进制文件的读写:
//创建文件流
fstream ofs("person.txt", ios::out | ios::in | ios::binary);
Person p = {"张三", 18}
//写文件
ofs.write((const char *)&p, sizeof(p));
//读取文件
ifs.read((char *)&p,sizeof(p));
判断文件是否达到了末尾:
eof(): 表示如果读文件到达文件末尾,返回true
while (!ifm.eof()){}
实际是获得当前put和get流指针的位置,需将流指针位置设置到文件末尾
https://blog.csdn.net/lixiaoguang20/article/details/78911055
获取写入文件的大小:
ftellp()
获取读取文件的大小:
ftellg()
文件位置的设置:
seekg(pos, mode)是对输入流的操作 g是get缩写
seekp(pos, mode)是对输出流的操作 p是put缩写
pos:为int类型;可以为负
ios::beg:表示输入流的开始位置
ios::cur:表示输入流的当前位置
ios::end:表示输入流的结束位置
重命名文件
https://blog.csdn.net/qq_46056214/article/details/106606241
文件file.1.txt命名为file.2.txt
std::rename("./file.1.txt", "./file.2.txt");
删除文件:
std::remove("./file.2.txt");
判断文件谁否存在:
存在返回0
if(access("./file.2.txt", 0) != -1)
3. 函数的重载
所谓函数重载的实质就是用同样的名字再定义一个有着不同参数但有着同样用途的函数。
我们只能通过不同参数进行重载,但不能通过不同的返回值(尽管后者也是一种区别);
函数进行重载的目的是为了方便对不同数据类型进行同样的处理。
4. 类
1. 作用域解析符 ::
用于在类里面定义函数,类外面实现函数。
class Car
{
public:
void setColor(std::string col);
};
void Car::setColor(std::string col)
{
color = col;
}
2. C++允许在类里声明常量,但不允许对它进行赋值
class Car
{
public:
const float TANKSIZE = 85; // 出错@_@
}
绕开这一限制的方法就是创建一个静态常量
class Car
{
public:
static const float FULL_GAS = 85;
}
3. 构造器和析构器
在创建对象时,系统都会自动调用一个特殊的方法,即构造器。
相应地,在销毁一个对象时,系统也应该会调用另一个特殊方法,即析构器。
一般来说,构造器用来完成事先的初始化和准备工作(申请分配内存),析构器用来完成事后所必须的清理工作(清理内存)。
构造器和类一样的名字,名字比构造器前边多了一个波浪符“~”前缀。
构造器不会返回任何值。
析构器不会返回任何值,且没有参数。
如果没有定义构造器和析构器,则编译的时候会创建一个空的构造器和析构器。
class Car
{
Car(void);
~Car();
} ;
Car::Car(void)
{
}
Car::~Car(void)
{
}
4. 继承
继承机制使得程序员可以创建一个类的堆叠层次结构,每个子类均将继承在它的基类里定义的方法和属性。
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
1. 继承的方式
一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:
class derived-class: access-specifier base-class
其中,访问修饰符 access-specifier 是 public、protected 或 private 其中的一个,base-class 是之前定义过的某个类的名称。如果未使用访问修饰符 access-specifier,则默认为 private。
公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
2. 类的访问控制:
访问 | public | protected | private |
---|---|---|---|
同一个类 | yes | yes | yes |
派生类 | yes | yes | no |
外部的类 | yes | no | no |
继承参考文档:C++ 继承 | 菜鸟教程
3. 继承机制中的构造器和析构器
构造器的调用顺序:先调用基类的构造器,再调用子类的构造器。
析构器的调用顺序:先调用子类的析构器,再调用基类的析构器。
当基类构造器需要传递参数时,可以使用如下方式:
Pig::Pig( std::string theName ) : Animal( theName ){ }
4. 函数的覆盖/隐藏与重载
成员函数被重载的特征有:
1) 相同的范围(在同一个类中);
2) 函数名字相同;
3) 参数不同;
4) virtual关键字可有可无。
覆盖的特征有:
1) 不同的范围(分别位于派生类与基类);
2) 函数名字相同;
3) 参数相同;
4) 基类函数必须有virtual关键字。
隐藏是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
1) 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(若基类中定义了该函数的重载,所有该重载函数均会被隐藏掉)。
2) 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(若基类中定义了该函数的重载,所有该重载函数均会被隐藏掉)。
覆盖与隐藏的区别
被子类隐藏的方法,只能用基类指针访问该基类方法,子类指针访问该子类方法。基类指针访问其方法的构造函数不受影响。子类指针无法访问该方法在基类下的其他重载方法。
被子类覆盖的方法,使用基类和子类的指针访问该均会访问子类的方法。基类指针访问其方法的构造函数不受影响。子类指针无法访问该方法在基类下的其他重载方法。
子类对基类的方法fun进行覆盖,在子类中对基类的fun方法进行引用时,使用如下方式:base_class::fun();
5. 友元关系
友元关系是类之间的一个钟特殊关系,这种关系不仅允许友元类和友元函数访问对方的 public 方法和属性,还允许友元访问对方的 protected 和 private 方法和属性。
在类声明里的某个地方加上一条 friend class class_name或friend 函数申明;
6. 类的静态属性和静态方法
创建一个静态属性和静态方法: 只需要在它的声明前加上 static 保留字即可。
使用静态属性的时候,千万不要忘记为它们分配内存。具体做法很简单,只要在类声明的外部对静态属性做出声明(就像声明一个变量那样)即可。
静态方法不是属于某个特定的对象,而是由全体对象共享的,这就意味着它们无法访问 this指针。所以,我们无法在静态方法里访问非静态的类成员。
静态方法也可以使用一个普通方法的调用语法来调用,但建议不要这么做,那会让代码变得更糟糕! 请坚持使用:ClassName::methodName()的方式
class Pet
{
public:
Pet(std::string theName);
~Pet();
static int getCount();
private:
static int count;
};
int Pet::count = 0; // 注意这一句:他起码做了两件事; 1. 分配count的内存;2. 初始化count
7. 虚方法
声明一个虚方法的语法非常简单,只要在其原型前边加上 virtual 保留字即可。
虚方法是继承的,一旦在基类里把某个方法声明为虚方法,在子类里就不可能再把它声明为一个非虚方法了。
class Pet
{
public:
Pet(std::string theName);
virtual void play();
protected:
std::string name;
};
class Cat : public Pet
{
public:
Cat(std::string theName);
void play();
};
class Dog : public Pet
{
public:
Dog(std::string theName);
void play();
};
Pet::Pet(std::string theName)
{
name = theName;
}
void Pet::play()
{
std::cout << name << "正在玩儿!\n";
}
Cat::Cat(std::string theName) : Pet(theName)
{
}
void Cat::play()
{
Pet::play();
std::cout << name << "玩毛线球!\n";
}
Dog::Dog(std::string theName) : Pet(theName)
{
}
void Dog::play()
{
Pet::play();
std::cout << name << "正在追赶那只该死的猫!\n";
}
int main()
{
//cat 和 dog 在编译时都是 Pet 类型指针,编译器就认为两个指针调用的 play() 方法是Pet::play() 方法;
//引发问题的源头就是我们使用了 new 在程序运行的时候才为 dog 和 cat 分配 Dog 类型和 Cat 类型的指针;
//为了让编译器知道它应该根据这两个指针在运行时的类型而有选择地调用正确的方法(Dog::play() 和 Cat::play()),我们必须把这些方法声明为虚方法。
Pet *cat = new Cat("加菲"); //分配的是cat类的
Pet *dog = new Dog("欧迪");
cat -> play();
dog -> play();
delete cat;
delete dog;
return 0;
}
8. 抽象方法与抽象类
抽象方法(也可以叫做纯虚函数):在声明的虚方法末尾加上”=0”。
包含抽象方法的类叫做抽象类(接口),抽象类无法进行实例化。
9. 多态
多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
#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()
{
cout << "Parent class area :" <<endl;
return 0;
}
};
class Rectangle: public Shape{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape{
public:
Triangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
// 程序的主函数
int main( )
{
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->area();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
shape->area();
return 0;
}
输出结果:
Rectangle class area : Triangle class area :
在上例中我们调用了两次shape->area(),但是由于不同的类地址存储在shape,输出的结果不一样。
使用同样的调用方法,输出多种不同的结果,这就是多态。
10. 运算符的重载
重载运算符的函数一般格式如下:
函数类型 operator运算符名称(形参表列) { 对运算符的重载处理 }
例如我们可以重载运算符 + , 如下: int operator+(int a, int b) { return (a – b); }
class Complex
{
public:
Complex();
Complex(double r, double i);
Complex operator+(Complex &d);
void print();
private:
double real;
double imag;
};
Complex::Complex()
{
real = 0;
imag = 0;
}
Complex::Complex(double r, double i)
{
real = r;
imag = i;
}
Complex Complex::operator+(Complex &d)
{
Complex c;
c.real = real + d.real;
c.imag = imag + d.imag;
return c;
}
void Complex::print()
{
std::cout << "(" << real << ", " << imag << "i)\n";
}
最大公约数的计算
依据定理: gcd(a,b) = gcd(b,a mod b)
参考文档:欧几里德算法(辗转相除法) - 鼬与轮回 - 博客园
// 欧几里德算法(辗转求余原理)
void normalize()
{
// 欧几里德算法
int a = abs(numerator);
int b = abs(denominator);
// 求出最大公约数
while( b > 0 )
{
int t = a % b;
a = b;
b = t;
}
// 分子、分母分别除以最大公约数得到最简化分数
numerator /= a;
denominator /= a;
}
11. 多继承
1. 在使用多继承的时候,一定要特别注意继承了基类的多少个副本。
2. 在使用多继承的时候,最安全最简明的做法是从没有任何属性且只有抽象方法的类开始继承。(这样的类又叫做接口(interface))
3. 多继承格式
基本语法: class TeachingStudent : public Student, public Teacher { … }
12. 虚继承
虚继承是为了解决多继承中出现多少个基类副本的情况。
通过虚继承某个基类,就是在告诉编译器:从当前这个类再派生出来的子类只能拥有那个基类的一个实例。 虚继承的语法: class Teacher : virtual public Person { … }
#include <iostream>
#include <string>
class Person
{
public:
Person(std::string theName);
void introduce();
protected:
std::string name;
};
class Teacher : virtual public Person
{
public:
Teacher(std::string theName, std::string theClass);
void teach();
void introduce();
protected:
std::string classes;
};
class Student : virtual public Person
{
public:
Student(std::string theName, std::string theClass);
void attendClass();
void introduce();
protected:
std::string classes;
};
class TeachingStudent : public Student, public Teacher
{
public:
TeachingStudent(std::string theName, std::string classTeaching, std::string classAttending);
void introduce();
};
Person::Person(std::string theName)
{
name = theName;
}
void Person::introduce()
{
std::cout << "大家好,我是" << name << "。\n\n";
}
Teacher::Teacher(std::string theName, std::string theClass) : Person(theName)
{
classes = theClass;
}
void Teacher::teach()
{
std::cout << name << "教" << classes << "。\n\n";
}
void Teacher::introduce()
{
std::cout << "大家好,我是" << name << ", 我教" << classes << "。\n\n";
}
Student::Student(std::string theName, std::string theClass) : Person(theName)
{
classes = theClass;
}
void Student::attendClass()
{
std::cout << name << "加入" << classes << "学习。\n\n";
}
void Student::introduce()
{
std::cout << "大家好,我是" << name << ", 我在" << classes << "学习。\n\n";
}
TeachingStudent::TeachingStudent(std::string theName,
std::string classTeaching,
std::string classAttending)
:
Teacher(theName, classTeaching),
Student(theName, classAttending),
Person(theName)
{
}
void TeachingStudent::introduce()
{
std::cout << "大家好,我是" << name << "。我教" << Teacher::classes << ", ";
std::cout << "同时我在" << Student::classes << "学习。\n\n";
}
int main()
{
Teacher teacher("小甲鱼", "C++入门班");
Student student("迷途羔羊", "C++入门班");
TeachingStudent teachingStudent("丁丁", "C++入门班", "C++进阶班");
teacher.introduce();
teacher.teach();
student.introduce();
student.attendClass();
teachingStudent.introduce();
teachingStudent.teach();
teachingStudent.attendClass();
return 0;
}
13. 编程建议
1. 开始写代码前先画流程图
2. 编译错误不要立刻修改源代码,应该先完整地审阅一遍源代码,再开始纠正错误。因为冒失地修改源代码往往会造成错误越改越多、心情越改越乱的纠结局面。
3. 注意检查最基本的语法
4. 把可能有问题的代码行改为注释
5. 认真对待编译器给出的错误/警告信息
6. 检查自己是否已经把所有必要的头文件全部 include 进来
14. 错误处理
参考资料:C++ 异常处理 | 菜鸟教程
15. 动态内存管理
参考资料:C++ 动态内存 | 菜鸟教程
pvalue = net int; delete pvar; //分配变量或类
pvalue = new int[20]; delete [] pvalue; //分配数组
16. 强制类型转换
c语音中的方法
Company *company = new TechCompany("APPLE", "Iphone");
TechCompany *tecCompany = (TechCompany *)company;
C++中的方法
Company *company = new Company("APPLE", "Iphone");
TechCompany *tecCompany = dynamic_cast<TechCompany *>(company); //转换失败返回NULL
if( tecCompany != NULL )
{
std::cout << "成功!\n";
}
17. 命名空间
参考资料:C++ 命名空间 | 菜鸟教程
18. 函数模板
参考资料:C++ 模板 | 菜鸟教程