1、基本概念
1.1 引用类型(reference)
对一个数据可以使用"
引用
"
(
reference
)类型,这是
C++
对
C
的一个重要扩充,引用是一种新的变量类型,它的作用是为一个变量起一个别名
int a; int &b = a;
在声明变量 b
时变量
a
的引用后,在它们所在函数执行期间,该引用类型变量
b
始终与其代表的变量
a
相联系,不能再作为其他变量的引用(
别用
)
,意思是
"
企图使
b
又变成其他变量的引用是不行的
"
。
C++之所以增加引用类型,主要是把它作为函数参数,以扩充函数传递数据的功能。指针变量要另外开辟内存单元,其内容是地址,而引用变量不是一个独立的变量,不单独占内存单元。这种传递方式比使用指针比变量简单、直观、方便。使用变量的引用,可以代替部分指针的操作。引用不仅可以用于变量,也可以用于对象。
示例代码:
#include <iostream>
using namespace std;
//typedef //类型重定义
/*
C语言使用指针存储变量的地址 可以由指针对变量操作
C++语言弱化指针的概念,引入了 引用 的概念
格式:类型 &引用变量名 = 需要引用的变量
[C语言中的空指针NULL C++中空指针nullptr]
*/
void exchange(int &x, int &y);
int main()
{
/*
int a = 10;
int *p = &a; //int*类型的指针p指向int类型变量
*p = 50; //将a改为50
int &b = a;
cout << b << endl; //引用就是给变量起别名,引用必须和要引用的变量是同类型
char m = 'A';
char &n = m;
cout << n << endl; //通过引用也可以修改原有变量的内容
n = 'a';
cout << n << endl;
*/
int a = 10;
int b = 40;
int &c = a;
//c = b; //引用无法修改指向
exchange(a, b);
cout << a << " " <<b << endl;
return 0;
}
/* 引用的产生主要是为了函数传参(传参类的对象) */
void exchange(int &x, int &y){
int temp = 0;
temp = x;
x = y;
y = temp;
}
/*
引用 和 指针的区别
1. 指针是一个变量,占8字节(64系统),系统会为其分配至地址
引用不是变量,不占空间,只是原本变量的别名
2. 指针可以修改指向,但是引用一旦定义就无法修改指向,因为依赖于变量存在
3. 指针定义的时候可以不初始化,但是引用定义的同时必须初始化
*/
2.2 内置函数(inline-function)
C++提供一种提高效率的方法,即在编译时将所调用函数的代码直接嵌入到主调函数中,而不是将流程转出去。这种嵌入到主调函数中的函数称为内置函数(inline function
),又称内嵌函数。指定内置函数的方法很简单,只需在函数首行的左端加一个关键字 inline
即可。
内置函数中不能包括复杂的控制语句,如循环语句和 switch
语句。
应当说明:对函数作 inline
声明,只是程序设计者对编译系统提出的一个建议,也就是说它是建议性的,而不是指令性的。并非一经指定为 inline
,编译系统就必须这样做。编译系统会根据具体情况决定是否这样做,例如对前面提到的包含循环语句和 switch
语句的函数或一个递归函数是无法进行代码置换的,又如一个
1000
行的函数,也不大可能在调用点展开。此时编译系统就会忽略 inline
声明,而按普通函数处理。
总结,只有那些规模较小而又被频繁调用的简单函数,才适合于声明为内置函数。
示例代码:
#include <iostream>
using namespace std;
/*
内置函数 / 内联函数 / 内嵌函数
称以下体积较小并且逻辑简单,调用次数较多的函数为内置函数
如果函数体内部逻辑复杂,while,for,if等判断循环分支语句都不建议设置为内置函数
格式:在函数名称前加上关键字inline
[加上关键字知识给编译器提建议,具体是否将函数设置为内置函数还是要看编译器的判断结果]
[如果函数过于复杂,编译器将函数看作普通函数对待]
*/
/*
如果在程序中要重复多次的执行简单动作,例如函数
跳转时间 1ms
函数执行时间 0.5ms
函数如果在程序中调用100次 跳转时间一共就100ms
如果函数体转换时间花费较多可以将函数设置为内联函数
实现过程:
将实参传给形参,将形参拷贝到函数体内部
再将函数体内部执行程序替换到主程序中调用该函数的位置
*/
inline void printInfo(string name, int id){
cout << name << " " << id << endl;
}
int main(){
printInfo("A", 1000);
printInfo("B", 1001);
printInfo("C", 1002);
cout << "D" << " " << 1003 << endl;
//....
return 0;
2.3函数重载(function-overloading)
C++允许用同一个函数名定义多个函数,这些函数的参数个数和参数类型不同,这就是函数的重载。即对一个函数名重新赋予它新的含义,使一个函数名可以多用。
重载函数除了允许参数类型不同以外,还允许参数的个数不同。
2.4函数模板(function-template)
C++提供了函数模板。所谓的函数模板,实际上是建立一个通用函数,其函数类型和参数类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板,凡是函数体相同的函数都可以用这个模板来代替,不必定 义多个函数,只需再模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
通用类型定义
template <typename XXX>
template 的含义是"模板",
<>中先写关键字 typename 或 class ,后边跟一个类型参数
XXX,类似标识符, 是一个虚拟的类型名
2.5 动态内存分配
在软件开发的过程中,常常需要动态地分配和撤销内存空间、例如对动态链表中节点的插入和删除。C
语言中 是利用库函数 malloc
和
free
来分配和撤销内存空间的,而
C++
则提供了较为简便且功能较强的运算符
new
和 delete 来取代
malloc
和
free
函数。
需要注意的是,new 和 delete 是运算符,不是函数,因此执行效率更高。为了与 C 语言兼容,C++仍保留了malloc 和 free 函数,但不建议使用,更推荐使用 new 和 delete
/* 开辟一个存放整型的存储空间,返回一个指向该存储空间的地址(指针) */
int *p = new int;
/* 开辟一个存放整型的存储空间,并指定该整数的初值为 100 */
int *p = new int(100);
/* 开辟一个存放二维整型数组的空间,返回首元素的地址 */
int (*p)[4] = new int[5][4];
new 运算符使用的一般格式为:new 类型(初值) 用 new 分配数组空间时不能指定初值。
如果由于内存不足等原因而无法正常分配空间,则 new 会返回一个空指针 NULL,用户可以根据该指针的值判 断分配空间是否成功。 delete 运算符使用的一般格式为:delete []指针变量 对基本数据类型的申请可以直接释放。
如果是类似"char *pt = new char[10]"申请的字符数组空间(构造数据类型空间),释放时应在指针变量前 加一对方括号,表示是对数组空间的操作:delete []pt;
#include <iostream>
using namespace std;
/*
C语言中使用malloc free进行堆区空间的申请和释放
void * malloc()
C++ 使用new delete
*/
typedef struct Person{
string name;
int age;
}P_t;
int main(){
int *p = new int; //申请4字节空间
int *q = new int(50); //申请4字节空空间的同时初始化
*p = 100;
cout << *p << endl; //100
cout << *q << endl; //50
int *t = new int[10]; //申请10个连续的4字节空间
for(int i=0; i<10;i++){ //循环初始化
*(t+i) = i;
}
for(int i=0; i<10;i++){
cout << *(t+i) << endl;
}
int *T = new int[5]{8,5,2,3,0}; //申请同时初始化
for(int i=0; i<5;i++){
cout << *(T+i) << endl;
}
struct Person *L = new P_t{("Zhang"), 90};
delete p;
delete q;
delete []t; //动态申请的连续空间在释放时要加上[]
delete []T;
delete L;
return 0;
}
2、概念升级
2.1 类的定义和使用
定义类需要使用关键字
class
,然后指定类的名称。类的主体是包含在一对花括号中,其中包含成员变量和成员函数。定义一个类,本质上是定义一个数据类型的蓝图,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。
class 类名
{访问修饰符: //
属性 type name; //
方法 type method(); };
// 和结构体类型定义相同 类的定义需要以分号结尾
类用来创建对象,对象中的数据成员可以使用运算符"."
进行访问,类不仅可以创建对象变量,也可以创建对象指针,对象指针访问数据成员则使用运算符"
->
"
,这一点和结构体类似。
示例代码:
#include <iostream>
using namespace std;
/*
* 在C语言结构体中不能定义或者实现函数,在c++中结构体可以
* 在类中也可以直接定义以及实现函数
* 类 class
* 类中的内容:分为两种变量(属性)和函数(方法)
* 定义类的格式:
* class 类名{
* //类中的成员函数以及成员变量
* }
*
* 类中成员有三种访问修饰符 public protected private
* public:公开成员,类内类外都可以访问
* protected: 保护成员,类内可以访问,类外不能访问,但是继承类可以访问
* private:私有成员,类外不可访问,继承类也不能,友元类和友元函数可以访问
*
* 类中如果没有任何访问修饰符,那么全部成员是私有的
*/
class myclass{
public:
string name;
int age;
friend void friendFunc(); //声明友元函数,此类中的私有成员在友元函数种可以访问问
friend class fclass;
void inputInfo(string t_name, int t_age){
name = t_name;
age = t_age;
}
void ouptInfo(){
cout << "name=" + name << " " << "age=" << age <<endl;
}
private:
int private_num;
};
void friendFunc(){
myclass user2;
user2.private_num = 100; //在友元函数种访问私有成员
}
int main()
{
//使用自定义的类定义对象 -- 实例化类
myclass user;
//调用成员函数对成员变量赋值
user.inputInfo("Zhang", 20);
user.ouptInfo();
return 0;
}
2.2构造函数/析构函数
myclass.h:
#ifndef MYCLASS_H
#define MYCLASS_H
#include <iostream>
using namespace std;
/*
* 构造函数和析构函数由系统生成并调用,不由用户调用
* 构造函数在对象生命周期开始的时候被调用
* 析构函数在生命周期结束时候被调用
*/
class myclass{
public:
//无参构造函数-默认
myclass(); //构造函数函数名和类名相同,没有返回值,可以传参数,可以被重载
~myclass(); //构造函数函数名是类名加上~,没有返回值,不可以传参数,不可以被重载
//如果在类内显式声明构造函数就要实现
myclass(string name, int age); //构造函数重载
myclass(string name, int age, char sex); //构造函数重载
myclass(const myclass &obj); //拷贝构造函数-将另外一个对象的全部内容拷贝给当前的对象
//编译器也会自动生成拷贝构造函数可以直接使用
void outputInfo(){ //该函数为公开函数,可以在类外访问该函数,间接访问或者修改私有成员
cout << name << endl;
cout << age << endl;
cout << sex <<endl;
//this->name; //在普通成员函数内可以使用this
}
static int num; //静态成员变量
static void static_func(){
//cout << this << endl; //静态成员函数中不能使用this
cout << "staic func " << endl;
}
protected:
private: //私有成员 类外无法访问 [友元函数可以]
string name = "Zhang";
int age;
char sex;
};
#endif // MYCLASS_H
myclass.cpp:
#include "myclass.h"
myclass::myclass(){
cout << "无参构造函数:" << name <<"生命周期开始" << endl;
}
myclass::~myclass(){
cout << "析构函数:" << name <<"生命周期结束" << endl;
}
//构造函数重载
myclass::myclass(string name, int age){
this->name = name; //在函数体内部初始化成员变量
this->age = age;
cout << "有参构造函数:" << name <<"生命周期开始" << endl;
}
//this指针:只能在类内的成员函数中使用,指向调用成员函数的对象,是一个无类型的指针(静态成员函数中不能用this指针)
//构造函数重载--形参初始化列表 -> 要初始化的成员名称(形参) 以逗号分隔开
myclass::myclass(string name, int age, char t_sex) : name(name), age(age), sex(t_sex){
cout << "有参构造函数:" << name <<"生命周期开始" << endl;
}
//拷贝构造函数 自定义并实现
myclass::myclass(const myclass &obj){
name = obj.name;
age = obj.age;
sex = obj.sex;
}
main.cpp:
#include "myclass.h"
//在c++源文件种,可以定义结构体在结构体定义并实现函数
//结构体种的函数默认的访问修饰符public
//也可以使用其他两个修饰符
//结构体类型 变量
//类 (数据类型) 对象(变量)
int myclass::num = 80; //静态成员函数必须在类外部定义一次
int main()
{
/*
myclass u1; //对象的生命周期开始
myclass u2("Wang", 50); //有参构造函数--重载
u2.outputInfo();
myclass u3("Liu", 10, 'm'); //有参构造函数--重载
u3.outputInfo();
myclass u4(u3); //拷贝 构造函数
u4.outputInfo();
*/
myclass user;
myclass::static_func(); //静态成员变量以及静态成员函数访问方式
//1. 使用类定义对象,通过对象进行访问
//2. 直接使用类加上访问限定符访问静态成员(变量/函数)
cout << myclass::num++ << endl; //80
cout << myclass::num++ << endl; //81
cout << myclass::num << endl; //82
return 0;
}
2.3继承/抽象类/虚函数/多态
#include <iostream>
using namespace std;
/* 基类 父类 */
class Parent{
public: //类内外
string name;
Parent(){
cout << "父类的无参构造函数" << endl;
}
Parent(int num){
this->private_num = num;
cout << "父类的构造函数重载" << endl;
}
void outputInfo(){
cout << name << endl;
cout << age << endl;
}
virtual void buy(){
cout << "父类 全价" << endl;
}
protected: //只能在类内访问和派生类中访问
int age;
private: //只能在类内访问 和 友元函数内
int private_num;
};
/* 派生类 子类 */
//格式 class 类名 : 继承方式 父类名称{}
class Child : public Parent{
//默认存在基类中公开的成员
//可以再定义一些派生类的成员
public:
Child(){
cout << "子类的无参构造函数" << endl;
}
virtual void buy(){ //覆盖 override重写
cout << "子类 半价" << endl;
}
};
class abclass{
public:
virtual void abfunc() = 0; //纯虚函数 含有纯虚函数的类叫做抽象类-无法实例化的类
};
//如果继承自抽象类的派生类不重写基类中的纯虚函数,那么派生类也是抽象类,无法实例化
class son_abclass: public abclass{ //抽象类的派生类
public:
virtual void abfunc() override{
cout << "这是派生类的重写" << endl;
}
};
void func(Parent &p){
p.buy();
}
//多态:不同对象调用同一接口时有不同的动作
int main()
{
//使用派生类定义对象 可以直接访问基类中的公开成员
//Child child1;
/*
Parent p1; //基类对象
Child c1; //派生类对象
Parent *P;
P = &p1;
P->buy(); //如果基类和派生类没有加上virtual,那么此处和79行P->buy()都会调用基类中的buy函数
P = &c1;
P->buy(); //如果在基类以及子类中的成员函数都没有使用virtual进行修饰==没有满足多态
//那么调用函数时会根据指针的类型调用,也就是说基类的指针指向子类成员(此时指针类型为基类)
//也会调用基类中的成员函数而不是派生类的成员函数
*/
/*
Parent p1; //基类对象
Child c1; //派生类对象
func(p1);
func(c1);
*/
return 0;
}
(仅供参考学习)