C++学习 ——Day 1,2
内容来自博客~
文章目录
first period
from C to C++
Basic Part
c++同c的不同:(三大性质、库)
抽象性、封装性、继承性、多态性;标准模板库、面向对象编程… …
C++的I/O是以字节流的形式实现的,流(stream)实际上就是一个字节序列。
4个标准库定义的IO对象:cin、cout、cerr、clog
命名空间-namespace:
实际上就是一个由程序设计者命名的内存区域,程序设计者可以根据需要指定一些有名字的空间域,把一些全局实体分别放在各个命名空间中,从而与其他全局实体分隔开来。
命名空间是ANSIC++引入的可以由用户命名的作用域,用来处理程序中常见的同名冲突。
new/delete 与 malloc/free比较
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R9FEhMKU-1629340677375)(C:\Users\gzz\AppData\Roaming\Typora\typora-user-images\image-20210818095821196.png)]
两者申请和释放要配对出现、free和delete释放内存后,没有把指针设置成NULL会导致“野指针”。
引用
- 定义引用时必须初始化;可以将一个引用赋予给某个变量;
- C语言中没有引用,C++中才有引用,引用一般用作函数的参数或者函数的返回值;提高使用效率,引用不占存储单元。
- 使用引用作为传递函数的参数,在内存中并没有产生实参的副本,他是直接对实参操作。
- 如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。
- 如果既要利用引用调高使用效率,又要保护传递给函数的数据不在函数中被改变,就应当使用常引用。
如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率,有些场合不可以。
常引用:保护传递给函数的数据不在函数中被改变;
非const引用只能绑定到该引用同类型的变量。 int &p = 20; 错--类型不匹配
const引用可以绑定到不同但相关类型的对象,或者绑定右值。const int &p = 20; 对
类型不同—>保存副本,占用空间,再次修改其值时,const int &p的值不变
类型相同—>不占用空间
不允许返回局部变量的地址—不允许返回的引用对应于一个局部变量(局部变量在栈中)
函数
函数的四部分:返回类型、函数名、参数表、函数体
基础函数:内联函数、函数重载、模板函数、友元函数
成员函数:构造/析构函数、常成员函数、静态成员函数
内联函数inline:解决运行效率问题(编译时)
- 适用于函数体不大、但被频繁调用的函数的函数调用
- 内联函数不能有复杂的控制语句
- 递归函数不能是内联函数,类中的函数都是内联函数
- 内联函数有与带参数的宏定义相同的作用和相似的肌理,但他消除了宏定义的不安全性
重载:(多态性)
同一作用域内,函数名相同,参数的个数或类型不同。
带默认参数值的函数:
形参的默认值必须在声明中指定;默认值的定义应该从右到左;
注:形参的默认值不能为局部变量
second period
The encapsulation of the three major properties
封装性
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏
对象 = 属性 + 方法
数据成员 成员函数
三个存取权限关键字:public private protected
破坏封装性的关键字:friend
this指针只能在成员函数中使用。
this在成员函数的开始前构造,在成员函数的结束后清除。
this指针存在于类的成员函数中,指向被调用函数所在的类实例的地址。
变化:
struct Person
{
char *name;
int age;
}
Person per;
//c语言中
setName(&per,"xiao");
setAge(&per,20);
display(&per);
//c++语言中
per.setName("xiao");
per.setAge(20);
per.display();
//总结:少了一个参数 但多了一个this指针
类与对象
类
具有相同的属性和行为的对象抽象为类(class )
对象 :客观世界中任何一个事物都可以看成一个对象
类:具有相同的属性和行为的对象抽象为类(class )
类是对象的抽象、对象则是类的特例
- 权限:
- 将需要被外界调用的成员函数指定为public,它们是类的对外接口
- 私有的成员函数只能被本类中的其他成员函数所调用,而不能被类外调用
- 成员函数可以访问本类中任何成员(包括私有的和公用的),可以引用在本作用域中有效的数据
- 类体中只写成员函数的声明,而在类的外面进行函数定义
- 内置函数 inline
- 一般只将规模很小(一般为5个语句以下)而使用频繁的函数声明为内置函数;
- 对类内定义的成员函数,隐含地指定为内置函数。
- 使用内置函数可以节省运行时间,但却增加了目标程序的长度
对象
客观世界中任何一个事物
对象组成:
数据——描述对象的属性
函数——行为(操作代码),根据外界给的信息进行相应操作的代码外界给的信息进行相应操作的代码。
构造函数、析构函数、拷贝构造函数和赋值函数
构造函数
- 固定的函数名称类名( ) 、 没有返回类型、可以有参数、可以重载、一般由系统自动的调用
- 用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。(只负责为自己的类构造对象)
- 在构造函数的函数体中不仅可以对数据成员赋初值,而且可以包含其他语句;一般不提倡在构造函数中加入与初始化无关的内容 :
低耦合、高内聚
。
析构函数
- 固定的函数名称~类名( ) 、没有返回类型、没有参数、不可以重载、一般由系统自动的调用
- 在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用
- 如果用户没有定义析构函数,C++ 编译系统会自动生成一个析构函数,是徒有析构函数的名称和形式,实际上什么操作都不进行。
- 如果用户自己定义了一个析构函数,C++就不再提供默认的析构函数了
拷贝构造函数和赋值函数
拷贝构造函数 : 创建的同时进行初始化,调用构造函数
赋值函数 – 运算符重载 :对象已经创建和初始化,重新赋予值
MyString(const MyString &other);//拷贝构造函数
MyString &operator=(const MyString &other);//赋值函数
MyString str("orange");//调用拷贝构造函数
MyString str1 = str;//调用赋值函数
如果用户不自定义,系统会自动生成(构造函数/析构函数/拷贝构造函数/赋值函数);在类的数据成员没有指针时,可以不自定义拷贝构造函数/赋值函数,因为此时不涉及深浅拷贝。
深拷贝与浅拷贝:(前提:类的数据成员有指针,并且在堆中挂载一片儿内存空间)
浅拷贝、位拷贝 — > 缺省的(俩者指向同一内存空间)(linux下报错 double free释放)
深拷贝 —> 重新分配空间、(俩者指向不同内存空间)
总结:
- 当对象被创建时,构造函数被自动执行。→ 对象的初始化工作
- 当对象消亡时,析构函数被自动执行。→ 对象的清除工作
- 当数据成员中没有指针是可以不用实现
析构函数、拷贝构造函数和赋值函数
,此时不涉及深浅拷贝和释放内存。
类中的常量
const
-
如果参数是指针,且仅作输入用,则应在类型前加const,以防止该指针在函数体内被意外修改。
-
如果输入参数以值传递的方式传递对象,则宜改用“const&”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。
-
在C++中,传递一个参数时,通常先选择通过引用传递,而且是通过常量(const)引用
void setName(const char *name);
double getDistance(const Point &p1,const Point &p2);
- 常成员函数
用const修饰的函数,在声明和定义时都要加const
const int getAge() const;//类中的声明
const int Person::getAge() const//类外的定义
{
return m_age;
}
- 常数据成员
- const修饰的数据成员在每个对象中分配内存;(区别于常数:const修饰的为不可改变的变量)
- const修饰的数据成员生命周期是对象的生命周期,不同的对象其const数据成员的值可以不同。
- 不能在类声明中初始化const数据成员,const数据成员的初始化只能在类构造函数的初始化表中进行。
- 常对象
用const修饰的对象只能调用const成员函数
注:只有常成员函数才有资格操作常量或常对象,没有const关键字说明的函数不能用来操作常对象
(常成员函数的this指针是一个指向常量的指针 —> const Time *;普通的成员函数的this指针指向普通对象的指针 —> Time *若为const Time *则类型不匹配,即常对象不能调用普通成员函数)
const 的例外
- 把常函数中隐含的this指针强制转换为非const。
- 在类中定义数据成员时使用mutable
- const 所修饰的函数中,要由编译器负责保护类中的成员变量不被修改。
- mutable用来修饰类的成员变量,让该变量在const 所修饰的成员函数中可以被修改。
- const 修饰的函数只能是类的成员函数(不能为构造/析构函数);mutable修饰的变量只能是类的成员变量
友元
友元类 / 友元函数
友元函数:
友元类:
- 友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)
friend Time& operator++(Time&);//++a 在类中声明
Time& operator++(Time& t) // 在类外定义,与普通函数定义相同
{
cout << "Time& operator++(Time&)" <<endl;
//...
return t;
}
友元关系注意事项:
- 友元关系不能被继承。
- 友元关系是单向的,不具有交换性。
- 友元关系不具有传递性。
运算符重载 — 友元函数的应用<现实中唯一>
- 单目运算符: ++ –
- 双目运算符:+ - * / +=
- 三目运算符: ? :
重载的方法:友元函数 成员函数
友元函数:
对称的操作符,如算术运算、关系操作符、位操作符。(双目)
成员函数:
1. = [] () ->等必须定义为成员(+=不是必须)。 2. 改变对象状态或与给定类型密切联系的其他运算符,如自增、自减。
成员函数:形参看起来比操作数数目少1;
- a++ 与++a重载:用户在a++中多加一个int参数让编译器区分
friend Time& operator++ (Time& t);//++a 友元函数
friend Time operator++ (Time& t,int num);//a++ 友元函数
Time& Time::operator++ ();//++a 成员函数
Time Time::operator++ (int);//a++ 成员函数
Time t1 = t++;//t.operator++(0);
Time t1 = ++t;//t.operator++();
加号 + 重载
friend Time operator+ (const Time& t1,const Time& t2);//c = a + b 友元函数
Time Time::operator+ (const Time& t2) const;//a += b 成员函数
Time t = t1 + t2;//t1.operator+(t2);
加等于号 += 重载
friend Time& operator+= (Time& t1,const Time& t2);//a += b 友元函数
Time& Time::operator+= (const Time& t2);//a += b 成员函数
Time t = t1 += t2;
//t1.operator+=(t2);
//t1 = t1 + t2 ; t = t1;
数据流 - 输出流 << 输入流 >> 重载
ostream& operator<<(ostream& os,Time& t);//只有成员函数
istream& operator>>(istream& is,Time& t);
Time ttt;
cin >> ttt;
cout << ttt << endl;
重载的注意事项:
- 不要重载具有内置含义的操作符 (& , && , ||)
- 大多数操作符对类对象没有意义,根据需要将某些运算符重载
- 选择成员或非成员实现
警告: 不要滥用运算符重载
third period
Inheritance of the three major natures
面对对象编程
继承性(继承 – 事物的相似性)(扩展性、代码重用性)
UML 类图
关于StarUML的学习教程
StarUML(简称SU),是一种创建UML类图,生成类图和其他类型的统一建模语言(UML)图表的工具。StarUML是一个开源项目之一发展快、灵活、可扩展性强。
统一建模语言(UML,Unified Modeling Language)是面向对象软件的标准化建模语言。
作用:在软件的开发过程中,统一建模语言可以在整个设计周期中使用,帮助设计者缩短设计时间,减少改进的成本,使软硬件分割最优。
继承
-
继承与组合
类自动地获得另一个类的部分或全部的属性与操作;提高了代码的可重用性
is a :狗是动物 <类名 与 类名> 父类 与 子类
has a :狗有尾巴 <对象 与 对象> 父对象与子对象
- 内存模式
- 访问权限
private与protected的不同:
private允许类的成员函数访问,不允许在类外访问( 对象 );在继承中private在派生类中不允许访问;protected允许类的成员函数访问,不允许在类外访问( 对象 );在继承中protected允许在派生类的成员函数访问。对于类外而言,这两个是完全相同的。
基类和派生类
- 关系
> 1. 派生类是基类的具体化
> 2. 派生类是基类定义的延续
- 构成
构造函数和析构函数
构造函数:
• 对自己的数据成员进行初始化
• 负责调用基类构造函数使基类的数据成员得以初始化;
• 调用子对象的构造函数,对派生类中的子对象进行初始化;
调用顺序:基类的构造函数 → 子对象的构造函数 → 派生类构造函数体
析构函数:执行派生类的析构函数时,基类的析构函数也将被调用;
析构函数的执行顺序与构造函数严格相反
隐藏和覆盖:
C ++中的覆盖与隐藏
IF 子类的函数与父类的名称相同,但是参数不同
父类函数被隐藏
ELSE IF 子类函数与父类函数的名称相同&&参数也相同&&但是父类函数没有virtual
父类函数被隐藏
ELSE IF 子类函数与父类函数的名称相同&&参数也相同&&但是父类函数有virtual
父类函数被覆盖
upcasting(向上转型) 和 downcasting(向下转型)
c++必须在类初始化列表中初始化的几种情况
- const类型的类成员 — 常数据成员;
- 引用类型的类的数据成员;
- 没有默认构造函数的子对象;(子对象的初始化用对象名,而不是类名)
- 派生类在初始化列表中调用基类的构造函数;
多文件操作
若干.h / .cpp文件
Makefile描述工程所有文件的编译顺序和编译规则
析构函数的执行顺序与构造函数严格相反;
多继承和二义性
二义性的产生:1.重名定义 2. 多路径继承
解决二义性的方法:
- 不重名 — 利用成员名限定法(Bird与Horse中的fun 与 m_weight不重命)
- 在派生类中定义一个同名成员;(在FlyHorse中也定义fun 与 m_weight – 占用内存)
- 作用域限定二义性问题flyHorse.Horse::m_weight;
- 虚基类继承(避免多路径继承二义性)
虚基类:采用菱形继承比较好说明
虚继承中有虚基类表指针
虚继承,不包含虚函数时,新增虚基类指针,指向虚基类表,虚基类表中首项存储虚基类指针的偏移量,接下来依次存储虚基类的偏移量(偏移量是相对于虚基类表指针的存储地址)。
多态的实现
联编
多态性表现为以下几种形式:
- 重载多态:通过调用相同名字的函数,表现出不同的行为。运算符重载也是一种重载多态。
- 运行多态:通过基类的指针,调用不同派生类的同名函数,表现出不同的行为。
- 模板多态,也称为参数多态,通过一个模板,得到不同的函数或不同的类。
虚函数
动态绑定的基础
动态绑定又称滞后绑定,即在编译期并不确定函数调用语句的执行代码,而是为它生成虚函数表(哈希表)用以存放同名,同参,同返回值的虚函数地址。真正的绑定推迟到程序运行时再完成。在运行中遇到以指针调用虚函数的语句时,现场决定应执行的具体代码。
虚函数的实现机制
动态绑定是通过虚函数表实现的对于含有虚函数的多态,编译器为每个对象生成一个虚表指针,即在每个对象的内存映像中增加了一个**_vfptr指针**,它指向一个虚函数表vtable。例如在基类的虚函数表(哈希表)中列出基类所有虚函数的入口地址。
- 虚析构函数:使用delete运算符删除一个对象时,能保证析构函数被正确地执行;
- 继承时,要养成的一个好习惯就是,基类析构函数中,加上virtual
【说明】如果将基类的析构函数声明为虚函数时,由该基类所派生的所有派生类的析构函数都自动成为虚函数,即使派生类的析构函数与基类的析构函数名字不相同。
根据什么考虑是否把一个成员函数声明为虚函数?
- 成员函数所在的类是否会作为基类。
- 成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。不要仅仅考虑到作为基类而把类中的所有成员函数都声明为虚函数。
- 调用是通过对象名还是通过基类指针或引用去访问,如果是通过基类指针或引用去访问的,则应当声明为虚函数。
【说明】:使用虚函数,系统要有一定的空间开销。当一个类带有虚函数时,编译系统会为该类构造一个虚函数表,它是一个指针数组,存放每个虚函数的入口地址。系统在进行动态关联的时间开销很少,提高了多态性的效率。
纯虚函数
抽象类
java中没有这一特性,但以接口将以代替。参考:c++抽象类与接口
#include <iostream>
#include <string>
using namespace std;
class Shape
{
public:
virtual double getArea() const = 0;
void disply() const;
protected:
Shape(string name);
virtual ~Shape(){};
private:
string m_name;
};
Shape::Shape(string name)
{
m_name = name;
}
void Shape::disply() const
{
cout << "Area : " << getArea() <<endl;
}
class Circle:public Shape
{
public:
double getArea() const;
Circle(string name,int radius);
private:
int m_radius;
};
Circle::Circle(string name,int radius):Shape(name)
{
m_radius = radius;
}
double Circle::getArea() const
{
return 3.14 * m_radius * m_radius;
}
int main()
{
Circle cir("圆",5);
cir.disply();
return 0;
}
说明:
- 在Shape类中实现面积无意义,但在其派生的正方形、圆、三角形等中有意义;面积也是其派生类所共有的行为,所以定义为纯虚函数
- 有纯虚函数的类为抽象类;即Shape类为抽象类;
- 纯虚函数getArea() 在 基类Shape类中只申明,在派生类中实现;派生类若不实现getArea(),该派生类仍为抽象类。
- 抽象类不能实例化,既不能创建对象,因此一般将该类的构造函数说明为protected提醒程序员,而析构函数照常为public让其可以正常释放
类型转换符
//--》const_cast
const int ra = 100;
int &rb = const_cast<int&>(ra);
rb = 10;
//--》static_cast
int n = 6;
double d = static_cast<double>(n);//基本类型转换
//--》reinterpret_cast
int doSomething(){return 0;};
typedef void(*FuncPtr)();//typedef函数指针类型
FuncPtr funcPtrArray;//定义了一个函数指针
funcPtrArray = reinterpret_cast<FuncPtr>(&doSomething);
//--》dynamic_cast
class DerivedClass: public BaseClass
{
public:
void fun(){};
};
BaseClass *pd2 = dynamic_cast<BaseClass *>(pb);
//子类->父类,动态类型转换,正
DerivedClass *pd22 = dynamic_cast<DerivedClass *>(pb2);
//父类->子类,动态类型转换,安全的。结果是NULL
c++写链表、异质链表
fourth period
Polymorphism of three properties
同上。
fifth period
c++之标准I/O库
标准I/O库
输入输出对象 istream cin,ostream cout
格式化输入输出
cin >>
//输入cout <<
//输出hex、oct、dec和setbase
- 浮点精度
precision / setprecision
- 域宽
width / setw
- 用户自定义流操纵算子-
’\n’ ‘\t’ ‘\r’
格式化输出:
hex、oct、dec和setbase
非格式化输入/输出
//输出
ostream& put ( char c ); //写一个字符出去
ostream& write ( const char* s , streamsize n );//写指定长度的字符串出去
//输入
int get();
istream& get ( char& c );
istream& get ( char* s, streamsize n );
istream& get ( char* s, streamsize n, char delim );
istream& get ( streambuf& sb);
istream& get ( streambuf& sb, char delim );
istream& getline (char* s, streamsize n );
istream& getline (char* s, streamsize n, char delim );
istream& read ( char* s, streamsize n );
非格式化输出
非格式化输入:read() / getline() 同get()
文件操作
文件的打开方式:(ios = ios_base)
(默认in|out)
- ios_base::app:文件末尾处添加
- ios_base::ate:文件末尾处添加
- ios_base::binary:文件以二进制的形式打开
- ios_base::in:文件以写入的方式打开
- ios_base::out:文件以读出的方式打开
- ios_base::trunc:文件以擦除重写的方式打开
文件位置指针
用于指示读写操作所在的下一个字节号;是个整数值,指定文件中离文件开头的相对位置(也称为离文件开头的偏移量) 。
//获取文件指针
fstream file;
...
file.tellp();
file.tellg();
//文件指针的重新定位
file.seekp(10,ios::beg);
file.seeekg(10,ios::end);
- ios::beg(默认)相对于流的开头定位
- ios::cur相对于流当前位置定位
- ios::end相对于流结尾定位
streampos ostream::tellp ( ); //返回值可赋给long类型的变量
ostream& ostream::seekp ( streampos pos );
ostream& ostream::seekp ( streamoff off, ios_base::seekdir dir );
streampos istream::tellg ( );
istream& istream::seekg ( streampos pos );
istream& istream::seekg ( streamoff off, ios_base::seekdir dir );
//一个文件有一个指针,输入/输出复用
ios::operator! 与 ios::operator void
#include <iostream>
#include <iomanip>
using namespace std;
#if 0//进制的格式化输出
int main()
{
int number;
cout << "Enter a dec number: ";
cin >> number;
cout << hex << number << endl;
cout << oct << number << endl;
cout << dec << number << endl;
cout << setbase(8) << number << endl;
//hex oct dec setbase(8/10/16)修改了数据的格式
//在修改格式之前,原来的格式将一直有效
cout << number << endl;
return 0;
}
#endif // 0
#if 0//浮点的精度格式化输出
int main()
{
double root2 = 122.61241232;
// cout << fixed;
for (int places = 0; places <= 9;places++ )
{
cout.precision( places );
cout << root2 << endl;
// cout << setprecision(places) << root2 << endl;
}
return 0;
}
#endif // 0
#if 0//设置域宽的格式化输出
int main()
{
int widthValue = 4;
char str[10];
cout << "Enter a str:" << endl;
cin.width( 5 );
while (cin >> str)
{
cout.width( widthValue++ );
cout << right << str << endl;
//cout<<setw(widthValue++) << str << endl;
cin.width( 5 );
}
return 0;
}
#endif // 1
#if 0//自定义流的格式化输出
ostream &endLine(ostream &out)
{
out << '\n';
return out;
}
int main()
{
cout << "HelloWorld" << endLine << "!!!" << endLine;
return 0;
}
#endif // 0
#if 0//非格式化输出
int main()
{
// cout.put('h').put('i') << endl;
// cout.write("hello",2).write("world",20) << endl;
// cout << cin.get() << endl;
// char str;
// cin.get(str);
// cout << str << endl;
// char str1[10];
// cin.get(str1,6);
// cout << str1 << endl;
char str1[10];
cin.get(str1,6,' ');
cout << str1 << endl;
return 0;
}
#endif // 0
#if 0//文件操作
#include <fstream>
int main()
{
fstream file("hello.txt",ios::app|ios::in|ios::out);
if (!file)
{
cout << "File could not be opened" << endl;
return 0;
}
file << "hello_world" << endl;
file.close();
char str[50];
file.open("hello.txt",ios::app|ios::in|ios::out);
if (!file.is_open())
{
cout << "File could not be opened" << endl;
return 0;
}
file >> str;
cout << str << endl;
file.close();
return 0;
}
#endif // 0
< str << endl;
// char str1[10];
// cin.get(str1,6);
// cout << str1 << endl;
char str1[10];
cin.get(str1,6,' ');
cout << str1 << endl;
return 0;
}
#endif // 0
#if 0//文件操作
#include
int main()
{
fstream file(“hello.txt”,ios::app|ios::in|ios::out);
if (!file)
{
cout << “File could not be opened” << endl;
return 0;
}
file << “hello_world” << endl;
file.close();
char str[50];
file.open("hello.txt",ios::app|ios::in|ios::out);
if (!file.is_open())
{
cout << "File could not be opened" << endl;
return 0;
}
file >> str;
cout << str << endl;
file.close();
return 0;
}
#endif // 0
![标准I/O库](https://i-blog.csdnimg.cn/blog_migrate/60fac1106b060645096ad0b9cc68e2cc.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/5a4a1bab88584ccebd9d90827cfbf654.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81Mjc3NzUxMA==,size_16,color_FFFFFF,t_70#pic_center)
初次编写于2021年8月18,19日。