C++学习记录
C++基础语法
C++相对与C语言的特点
C语言只适合处理小规模的程序,大规模的程序可以用C++。
C++在命名空间、函数重载与运算符重载、引用、面向对象特性(封装、继承、多态)、泛型编程、异常处理、标准库STL几方面对C语言进行加强。
C++文件后缀`.cpp` ;头文件后缀`.hpp`;编译`g++``;
代码编写时,类的创建和方法声明在头文件里,方法在自己的.cpp文件里;
命名空间
- 命名空间用于解决符号名称冲突问题。
- 语法:
namespace <命名空间名字> { <代码> }使用:全局引用 using namespace <命名空间名字> ;局部引用 <命名空间名字>::<函数名、变量名、类名>操作符:: - 命名空间取别名
namespace mySpace2 = mySpace一个命名空间可以取多个别名,使用别名和原名无任何区别。 - 匿名命名空间
namespace { int x; }定义自己的命名空间时不需要名字,可以直接使用里面的函数或变量,但只在当前文件内生效。
C++中的函数
- 带默认值的函数:在C++中存在带默认值的函数,即使 不传参数也会按默认值参数运行;只有部分参数有默认值时,带默认值的参数必须放在参数末尾栏。
void addNum(int a,int b = 10) { ; } - 带占位参数的函数:为了函数以后拓展功能,解决C中不规范函数传参。规则:在函数定义的时候只写类型,不写变量名(此时无法使用这个参数);也可以给他添加默认值,但调用时必须传入这个参数。
int sumNum (int a,int b,int = 0 ) { ; } - 函数重载:提高函数的易用性。概念:函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表(个数、类型、数量)的函数;
void print(int i) { }
void print(string str) { }
void print(int i,striing str) { }
- 底层原理:编译器会将重载函数设置成不同的函数名,根据参数类型与个数进行匹配。
C++引用
- 作用:简化指针的操作;
- 概念:引用是给一个变量或者对象去一个别名,对引用操作与对其捆绑的变量或对象操作一样;
- 规则:
<类型> & <引用名> = <目标变量或对象名>;声明引用时必须同时初始化,且初始化后不能改变引用空间的位置;引用类型必须与目标变量或者对象的类型一致;不能将已有的引用名作为其他变量或对象的名字或别名; - 原理:使用引用时编译器会在底层生成一个指针,并自动进行*运算;本质是指针常量的使用:
int * const p; - 应用:主要用于传参(例如:数据交换);传参用引用可以提高性能,节省空间;(普通传参是复制,传引用不应复制)
- 函数返回:返回的数据必须为静态变量;引用返回,函数可以做左值,也可以做右值;
- 对数组引用:
int arr[5] = {0}; int (&refarr)[5] = arr; - 对指针引用:
int num = 10; int *p = # int *&refp = p;
new与delete
- new:申请内存并初始化对象;
- delete:释放内存并销毁对象;
- 用法:
nt *p = new int; *p = 10; delete p;数组:int *p = new int[5]; p[0] = 1; p[1] = 2; delete []p;new和delete都是操作符; - 与malloc/free的区别:malloc/free是C/C++标准库中的函数,new/delete是C++操作符;malloc/free需要手动计算类型大小,且返回void*,new/delete可以自己计算类型大小,根据类型返回相应类型的指针;new/delete的实现基于malloc/free;malloc/free只负责分配/释放空间,new/delete构造函数与构析函数,用于初始化对象与销毁对象。
C++输入与输出
- C++中的输入输出通过流的方式来实现
#include <iostream> using namespace std; - 流运算符:>> 输入运算符;<< 输出运算符;
- 定义:cin:标准输入对象(输入类型不匹配会返回空,而且消除了C中scanf连续输入多次字符时产生垃圾字符的缺点);cout:标准输出对象(设置域宽 为5:
cout.width(5));endl:换行操作; - 标志设置:

面向对象编程
面向对象
-
面向对象与面向过程:面向过程(PO):当解决问题时,把问题拆分成一个个函数和数据。然后按照一定的顺序执行完这些方法。面向对象(OO):解决问题时,把事物抽象成对象的概念,就是说这个问题里有哪些对象,然后给对象赋一些属性和方法,然后让每个对象执行自己的方法。
-
类与对象:类——对某一事物的抽象;对象——某一类事物的个体,具体且唯一;
-
类:在C++语言中是一种数据类型,用于描述某一类的事物,包括属性与方法(函数);
定义:class classname{ Access specifiers: <方法>;<变量>};;Access specifiers:访问修饰符public(公用的);private(私有的);protected(受保护的);

-
对象:具体化某个事物,成为一个具体的个体,创建对象后才会分配内存空间;
创建方式:普通:<类名> <对象名>;指针类型:<类名> *<对象指针名> = new <类名>(); delete <对象指针名>;
-
访问属性与方法:普通:
<对象名>.<属性或方法>;对象指针:<对象指针名>-><属性或方法>;
构造函数
-
构造函数:在构造对象时自动调用此函数,往往用来初始化对象内的成员变量;
-
定义:函数名与类名一致,且没有返回值;
<类名>(参数);;调用时<类名>::<类名/函数名>(参数) { ;};默认构造函数:当类中没有构造函数时,编译器会自动生成一个没有任何操作的构造函数;

3.构造函数重载:针对不同的初始化方式,可以构造重载函数;
-
初始化列表:在初始化时,也可以使用初始化列表的方式来实现构造函数;
<函数名>(参数列表) : <属性名1>(参数1),<属性名2>(参数2);
-
拷贝构造函数:当一个对象需要通过另一个对象初始化,则需要使用拷贝函数。
<类名>(const <类名> &other)
使用场景:主动创建对象时,用一个对象去初始化另一个对象的时候Circle a; Circle b = a;;一个对象以值传递的形式传入函数体;一个对象以值的形式从函数返回;
-
深拷贝与浅拷贝:浅拷贝:默认的拷贝构造函数为浅拷贝,针对指针对象,只拷贝指针存储的地址;深拷贝:针对只针对象,拷贝指针指向空间。



-
explicit:规范传参方式,不允许隐式转换;
析构函数
- 析构函数:在销毁对象时自动调用此函数,往往用来释放对象内申请的资源;
- 定义:与类名相同,在前面加
~,且不能有任何参数和返回值;
- 特点:没有参数,没有返回值,但又this指针;析构函数不能使用const修饰;一个类有且只有一个析构函数,所以不能重载
static
- static:static表示静态关键字,可以用来修饰成员变量与成员函数
<类名>::<静态成员名>; - 当修饰成员变量时:语法:在class中进行声明
static <类型修饰符><变量名>;在class外进行初始化<类型修饰符><类名>::<变量名> = <初始化值>;
特点:static修饰的成员变量属于类,不属于对象;static成员变量的内存在程序开始运行时分配,程序运行结束时释放内存;static成员变量对于所有的对象都是共享的,并在对象创建之前就已经产生了(因此,对象的静态成员变量输出都是相同的);静态成员使用前必须初始化,否则会在链接时出错;在成员函数中可以正常访问静态成员变量; - 当修饰成员函数时:语法:
static <函数返回值><函数名>(参数列表)
特点:可以直接被对象使用;可以通过类名直接发访问;不能使用this指针,与类关联,不与对象关联,再调用静态函数时,可以在没有对象时调用;不能访问非静态成员,只能访问静态成员;
4. 使用:一切不需要实例化(创建对象)就可以有确定行为方式的函数都应该设计成静态的;
this指针
- 作用:用于保存对象的地址,每一个非静态函数中都有一个隐藏参数this
- 常见使用方式:在非静态函数中,需要返回对象本身
return this;;在非静态函数中,传入形参名与对象内成员变量相同时区分两个变量;
const关键字
- const成员变量:const成员变量只能在初始化列表中初始化;

- const成员函数:防止成员函数修改非静态成员变量的值,const成员函数可以修改静态成员变量;


友元函数
- 概念:友元函数不是成员函数,它定义在类的外部,只需要在类中声明即可;友元函数可以访问所有私有成员和保护成员,一般情况下不去使用,使用不当会破坏程序面向对象的特性;
- 注意:友元函数不具有相互性,若类B时类A的友元,类A不一定时类B的友元;友元不能被继承;友元不具有传递性;
- 语法
friend <函数声明>;

- 示例:


运算符重载
- 简介:C++语言预定义的运算符只能操作基础数据类型,对于用户自定义的类型,在进行运算时也需要类似的操作;运算符重载实际上是对运算符赋予新的运算方式;
- 目的:使对象的运算操作更加简洁明了;
- 规则:大部分运算符可以重载,少部分不行:
'.' ; '.*' ,'->*' ; '::' ; 'sizeof' ; '?:' ; '#' ;; - 重载运算符可以对运算符号作出新的解释,但基本语义不变:无法改变运算符优先级;无法改变运算符结构性;无法改变运算符所需要的操作数;无法创建新的运算符;
- 语法:类的成员函数:
<函数返回值> operator<运算符>(形参表){ };类的友元函数:friend <函数返回值> operator<运算符>(形参表){ };


- 示例:

继承
继承
- 概念:继承是从已有的类创建新的了类的过程,这使得创建和维护一个应用程序变得容易,达到了重用代码功能和提高执行时间的效果;继承呈现了面向对象程序设计的层次结构,体现了从简单到复杂的认知过程;在C++类中,如类B继承于类A,则类A叫做基类或父类,则类B叫做派生类或子类;
- 语法:
class <子类>:<权限访问限定符> <父类名>,<权限访问限定符><父类名>...{ };
继承类型
- 继承类型:公有继承;保护继承;私有继承;
公有继承:当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被访问,但可以通过调用基类的公有和保护成员来访问。
保护继承:当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
私有继承: 当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。#include <iostream> using namespace std; class A { public: A() : i(i),j(j),k(k) {} int i; protected: int j; private: int k; }; class B : public A { public: void show() { cout << "i = " << i << endl; cout << "j = " << j << endl; // cout << "k = " << k << endl; } }; class C : protected A { public: void show() { cout << "i = " << i << endl; cout << "j = " << j << endl; // cout << "k = " << k << endl; } }; class D : private A { public: void show() { cout << "i = " << i << endl; cout << "j = " << j << endl; // cout << "k = " << k << endl; } }; class E : public C//保护后再被继承可以访问i,j,但不能访问私有成员k { public: void show() { cout << "i = " << i << endl; cout << "j = " << j << endl; // cout << "k = " << k << endl; } }; class F : public D//私有后再被继承,都不可以被访问(private和protected的区别) { public: void show() { // cout << "i = " << i << endl; // cout << "j = " << j << endl; // cout << "k = " << k << endl; } }; int main(int argc, const char *argv[]) { B b; b.show(); cout << "i = " << b.i << endl; cout << "==============================" << endl; C c; c.show(); // cout << "i = " << c.i << endl; cout << "==============================" << endl; D d; d.show(); // cout << "i = " << d.i << endl; cout << "==============================" << endl; E e; e.show(); // cout << "i = " << e.i << endl; cout << "==============================" << endl; F f; f.show(); // cout << "i = " << f.i << endl; cout << "==============================" << endl; return 0; } - 无法被继承的成员:父类的构造函数,拷贝构造函数,析构函数;父类的重载运算符;父类的友元函数;
继承之后成员函数的隐藏
- 基类与派生类有同名的函数,调用时总是调用子类的函数,此时父类成员函数被隐藏; 一般子类继承过来的函数不适合子类,或者需要拓展,则需要重写父类的函数;返回值可相同可不同;
- 若想显现父类成员函数
Person::show();; - 构造函数调用顺序:定义出一个派生类时:1、先调用基类构造函数(存在多个继承时,调用顺序按派生类继承顺序);2、成员对象的构造函数;3、派生类本身的构造函数;析构函数调用顺序相反
多继承与多重继承
- 多继承:允许一个派生类拥有多个基类
- 多重继承:如果派生类在继续派生一个新的类,则形成了多重继承;
- 多重继承的菱形问题:
数据冗余问题:当D类继承于B类与C类,B类与C类有一个共同的基类时,在创建D类的对象时,A类的构造函数创建了两个A类对象;
二义性问题:A类中成员变量,可以通过B和C去访问,此时会存在两个同种含义的变量;
解决方案:使用虚继承;这种设计极其复杂,对程序性能也有影响,绝不推荐使用这种继承方式。
多态
多态
- 对于同一个行为对于不同的对象,有不同的表现。是面向对象编程三大特性之一(封装,继承,多态);
- 在C++,一般针对一个行为只会有一个名称,是对类的行为在抽象,主要作用在于统一行为的接口,提高方法的通用性;
- 多态在C++中分为静态多态与动态多态;静态多态是基于函数重载与泛型编程实现的;动态编程是基于虚函数实现的
静态绑定(联编)与动态绑定
- 定义:动态绑定是指在运行时才确定具体需要调用的函数;静态绑定值程序编译结束后就已经确定了需要调用的函数;
- 作用:把不同的派生类的对象都当作基类对象来看,可以屏蔽不同子类对象之间的差异;提高程序的通用性来适应需求的不断变化;
虚函数与动态多态的实现
- 虚函数:
使用virtual关键字声明的函数,时动态多态实现的基础;
非类的成员函数不能定义为虚函数;
类的静态成员函数不能定于为虚函数;
构造函数不能定义为虚函数,但可以将析构函数定义为虚函数;
当将基类的某一成员函数声明成虚函数后,派生类的同名函数自动成为虚函数; - 实现:
(1)、创建两个类,并且是继承关系;
(2)、基类中的函数声明成virtual函数,也就是虚函数;
(3)、派生类继承基类并重写基类中的虚函数;
(4)、通过基类的指针或引用访问基类的对象或者派生类的对象;
覆盖、重载、隐藏
- 覆盖(重写):成员函数覆盖(override):
定义:派生类重新实现基类的虚函数;
特点:不同的作用域(分别位于派生类与基类);函数名相同;参数相同;返回值相同;基类必须有virtual关键字,不能有static;重写函数的权限访问限定符可以不同; - 函数重载(overload):
定义:同一作用域内的函数名相同,参数不同的多个函数间构成重载;常成员函数与非常成员函数也可以构成重载(const);
特点:同一个作用域;函数名相同;参数不同;返回值可相同可不同;返回值可相同可不同;virtual关键字可有可无; - 成员函数隐藏(hiding):
定义:基类与派生类有同名函数,调用时总是调用派生类的函数,此时父类成员函数被隐藏;
特点:不在同一作用域;函数名相同;返回值可相同可不同;参数不同时,不论有无virtual关键字,基类函数将被隐藏(注意与重载的区别,重载在同一种作用域);参数相同时,但基类没有virtual关键字,基类的函数被隐藏(与覆盖区分,覆盖必须有virtual); - 隐藏与覆盖的区别:
相同点:子类对象在调用的时候,总是调用子类实现成员函数;
不同点:用父类的指针指向子类对象的时候,覆盖的情况下,父类指针调用的函数是子类实现的函数;而在隐藏的情况下,父类指针调用的函数时父类实现的函数;
虚析构函数
- 建议将基类的析构函数设置为虚函数,基类的析构函数声明为虚函数,则派生类的析构函数自动为虚函数;
- 当基类指针指向派生类对象时,如果析构函数不是虚函数,则不会发生动态多态,而导致只会调基类的析构函数,造成内存泄露;
- 示例:
#include <iostream>
using namespace std;
class A
{
public:
A() : i(new int(10))
{
cout << "A 构造" << endl;
}
virtual ~A()
{
cout << "A 释放" << endl;
delete i;
}
private:
int *i;
};
class B : public A
{
public:
B() : j(new int(20))
{
cout << "B 构造" << endl;
}
~B()
{
cout << "B 释放" << endl;
delete j;
}
private:
int *j;
};
int main(int argc, const char *argv[])
{
B *b = new B;
//delete b;
return 0;
}
虚函数原理与虚函数表
- 原理:
(1)、C++能够在运行时确定调用的函数,是因为引入了虚函数;
(2)、一旦类中引入了虚函数,在程序编译期间就会创建虚函数表,表中每一项数据都是虚函数的入口地址;
(3)、为了将对象与虚函数表关联起来,编译器会在对象中增加一个指针成员用于存储虚函数表的位置;
(4)、基类的指针指向派生类对象时就是通过虚函数表的指针来找到实际应该调用的函数; - 虚函数表原理分析:
基类与派生类都维护自己的虚函数表,如果派生类重写基类的虚函数,则虚函数表存储的是派生类的函数的地址; - 示例:
#include <iostream>
using namespace std;
class A
{
public:
virtual void func1()
{
cout << "A func1" << endl;
}
virtual void func2()
{
cout << "A func2" << endl;
}
virtual void func3()
{
cout << "A func3" << endl;
}
};
class B : public A
{
public:
void func1()//重写
{
cout << "B func1" << endl;
}
void func2()
{
cout << "B func2" << endl;
}
void func3()
{
cout << "B func3" << endl;
}
};
typedef void (*pnf)();//定义一个指针函数
int main(int argc, const char *argv[])
{
B b;
A &a = b;//将虚地址表换成了子类的
cout << "指向虚函数表指针的地址:" << (long *)&a << endl;
cout << "虚函数表的地址:" << (long *)*(long *)&a << endl;
cout << "虚函数表内第二个元素的地址:" << (long *)*(long *)&a + 1 << endl;
cout << "虚函数表内第三个元素地址:" << (long *)*(long *)&a + 2 << endl;
pnf p = (pnf)*((long *)*(long *)&a + 2);//pnf(虚函数入口地址)
p();
return 0;
}
抽象类与纯虚函数
- 抽象类相关定义:含纯虚函数的类成为抽象类;
纯虚函数:指定函数接口规范,而不做具体实现,实现部分由继承他的子类去实现; - 抽象类的特点与作用:
(1)、抽象类中值声明函数接口,不能有具体实现;virtual double area() = 0;
(2)、抽象类不能创建对象,可以定义指针的引用;
(3)、派生类继承基类,并且必须要实现基类中所有的纯虚函数,否则派生类也是抽象类; - 应用场景:
(1)、某些情况下父类只知道其子类应该包含怎样的方法,但无法准确知道子类如何实现这些方法。此时我们会在父类中声明相应的方法,而不去具体实现,让子类根据自己实际情况去实现相应的方法;
(2)、从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为子类的模板,避免子类设计随意性; - 示例:
#include <iostream>
using namespace std;
class Quadrangle
{
public:
virtual ~Quadrangle()
{
}
virtual int calcArea() = 0;//纯虚函数
virtual int calcPerimeter() = 0;
};
class Rhombus : public Quadrangle //菱形
{
public:
Rhombus(int i = 0,int h = 0) : i(i),h(h)
{
}
int calcArea()
{
return i*h;
}
int calcPerimeter()
{
return 4 * i;
}
private:
int i;//底
int h;//高
};
class Rect : public Quadrangle //菱形
{
public:
Rect(int h = 0,int l = 0) : h(h),l(l)
{
}
int calcArea()
{
return h*l;
}
int calcPerimeter()
{
return 2 * (h + l);
}
private:
int h;//宽
int l;//长
};
int main(int argc, const char *argv[])
{
Rhombus r(1,2);
cout << r.calcArea() << endl;
cout << r.calcPerimeter() << endl;
Rect e(3,4);
cout << e.calcArea() << endl;
cout << e.calcPerimeter() << endl;
return 0;
}
248

被折叠的 条评论
为什么被折叠?



