c++查漏补缺

c语言的struct只能包含变量,而c++中的class除了包含变量,还可以包含函数。
通过结构体定义出来的变量还是变量,而通过类定义出来有了新的名称,叫做对象。C语言中,会将重复使用或具有某项功能的代码封装成一个函数,将拥有相关功能的多个函数放在一个源文件,再提供一个对应的头文件,这就是模块。使用模块时,引入对应的头文件就可。而c++中,多了一层封装,就是类。类由一组相关联的函数,变量组成,你可以将一个类或多个类放在源文件,使用时引入对应的类就可以。如下图:

在这里插入图片描述
不要小看类(Class)这一层封装,它有很多特性,极大地方便了中大型程序的开发,它让 C++ 成为面向对象的语言。

c和c++中全局const变量的作用域相同,都是当前文件,不同的是他们的可见范围:
c语言中const全局变量的可见范围是整个程序,在其他文件中使用extern声明后
就可以使用;而c++const全局变量的可见范围仅限于当前文件,在其他文件中不
可见,所以它可以在头文件中,多次引入后也不会出错。
int *p = new int;  //分配1个int型的内存空间
delete p;  //释放内存
int *p = new int[10];  //分配10个int型的内存空间
delete[] p;
内联函数:在函数调用处直接嵌入函数体的函数。可以提高效率,即在编译时将函数调用处用函数体替换,类似于c语言的宏展开。
指定内联函数:函数定义处增加inline关键字。如下例:
#include <iostream>
using namespace std;//内联函数,交换两个数的值
inline void swap(int *a, int *b){int temp;temp = *a;*a = *b;*b = temp;
}int main(){int m, n;cin>>m>>n;cout<<m<<", "<<n<<endl;swap(&m, &n);cout<<m<<", "<<n<<endl;return 0;
}

结果:
45 99↙
45, 99
99, 45
使用内联函数的缺点也是非常明显的,编译后的程序会存在多份相同的函数拷贝,如果被声明为内联函数的函数体非常大,那么编译后的程序体积也将会变得很大,所以再次强调,一般只将那些短小的、频繁调用的函数声明为内联函数。

在实际开发时,需要实现几个功能类似,但细节不同。如:交换两个变量的值,这两个变量有多种类型,可以是int,float,char,bool等。
对于每个不同类型都写一个函数,完成没有必要。
c++中允许多个函数拥有相同的名字,只要它们的参数列表(参数类型,参数个数和参数顺序)不同就可以,称为函数的重载。
创建对象:
Student liLei; //创建对象
Student allStu[100];  //创建对象数组使用对象指针
1.在栈上分配内存:
Student stu;
Student *pStu=&stu;2.在堆上创建对象
Student *pStu=new Student;
使用new在堆上创建出来的对象是匿名的,没法直接使用,必要时用一个指针指向它,再借助指针来访问它的成员变量或成员函数。重点讲解了两种创建对象的方式:一种是在栈上创建,形式和定义普通变量类似;另外一种是在堆上使用 new 关键字创建,必须要用一个指针指向它,读者要记得 delete 掉不再使用的对象。
类是创建对象的模板,不占用内存空间,而对象是实实在在的数据,需要内存来存储。对象被创建
就会在栈区或者堆区分配内存。编译器会将成员变量和成员函数分开存储:分别为每个对象的成员变量分配内存,但所有对象都共享同一段函数代码。

在这里插入图片描述

构造函数:
对成员变量进行初始化,在构造函数的函数体在对成员变量一一赋值,才可采用初始化列表、
1.成员变量的初始化与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。
2.初始化const成员变量的唯一方法就是使用初始化列表。
this指针,是一个const指针,它指向当前对象,通过它可以访问当前对象的所有成员。void Student::setname(char *name){this->name = name;
}this实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给this
静态成员变量:static修饰。
实现:多个对象共享数据的目标。
1.static成员变量属于类,不属于某个具体的对象,即使创建多个对象,也只为其分配一份内存。当
某个对象修改了该值,也会影响其他对象,
2.static成员变量必须在类声明的外部初始化。
public:static int m_total;  //静态成员变量int Student::m_total = 0;
3.static成员变量的内存既不是声明类时分配,也不在创建对象时分配,而是在类外初始化时分配。
4.static成员变量既可通过对象来访问,也可通过类来访问。
//通过类类访问 static 成员变量
Student::m_total = 10;
//通过对象来访问 static 成员变量
Student stu("小明", 15, 92.5f);
stu.m_total = 20;
//通过对象指针来访问 static 成员变量
Student *pstu = new Student("李华", 16, 96);
pstu -> m_total = 20;注意:static 成员变量不占用对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问。
静态成员函数:
与普通成员函数区别:普通成员函数有this指针,可以访问类中的任意成员;而静态成员函数没有this指针,
只能调用静态成员函数。
const成员函数:
可以使用类中的所有成员变量,但不能修改它们的值,主要是为了保护数据而设置。也称常成员函数。
1.需要在声明和定义的时候在函数头部的结尾加上const关键字。
//声明常成员函数char *getname() const;//定义常成员函数
char * Student::getname() const{return m_name;
}区分一下cosnt的位置:
1.在函数开头的cosnt用来修饰函数的返回值,表示返回值是const类型,也就是不能被修改,如const char * getname()2.函数结尾加const表示常成员函数,表示只能读取成员变量的值,而不能修改,如char * getname() const。在 C++ 中,const 也可以用来修饰对象,称为常对象。一旦将对象定义为常对象之后,就只能调用类的 const 成员
(包括 const 成员变量和 const 成员函数)了。
引用:
参数的传递本质是一次赋值的过程,赋值就是对内存进行拷贝。所谓内存拷贝,是指将一块内存上的数据复制到另一块内存上。
c/c++禁止在函数调用时直接传递数组的内容,而是强制传递数组指针。而对于结构体和对象没有这种限制,调用函数时既可传递指针,也可直接传递内容,为提供效率,建议指针。但是在 C++ 中,我们有了一种比指针更加便捷的传递聚合类型数据的方式,那就是引用。
引用:数据的一个别名,通过这个别名和原来的名字都能找到这份数据。例:
#include <iostream>
using namespace std;
int main() {int a = 99;int &r = a;cout << a << ", " << r << endl;cout << &a << ", " << &r << endl;return 0;
}

运行结果:
99, 99
0x28ff44, 0x28ff44

c++继承时的名字遮蔽问题
若派生类的成员(包括成员变量和成员函数)和基类中的成员重名,会遮蔽从基类继承过来的成员。要访问基类中的
成员函数,要加上基类类名进行访问。
类的构造函数没法被继承。派生类中,对于继承过来的成员变量的初始化工作也得由派生类的构造函数完成,但大部分基类都有private属性的成员变量,他们在派生类中无法访问。
解决方法:在派生类的构造函数中调用基类的构造函数。
代码:
#include<iostream>
using namespace std;
//基类People
class People{
protected:char *m_name;int m_age;
public:People(char*, int);
};
People::People(char *name, int age): m_name(name), m_age(age){}
//派生类Student
class Student: public People{
private:float m_score;
public:Student(char *name, int age, float score);void display();
};
//People(name, age)就是调用基类的构造函数
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
void Student::display(){cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<"。"<<endl;
}
int main(){Student stu("小明", 16, 90.5);stu.display();return 0;
}

运行结果为:
小明的年龄是16,成绩是90.5。

构造函数的调用顺序:先基类,再派生类。析构相反。
基类的指针也可以指向派生类的对象。例:
#include <iostream>
using namespace std;//基类People
class People{
public:People(char *name, int age);void display();
protected:char *m_name;int m_age;
};
People::People(char *name, int age): m_name(name), m_age(age){}
void People::display(){cout<<m_name<<"今年"<<m_age<<"岁了,是个无业游民。"<<endl;
}//派生类Teacher
class Teacher: public People{
public:Teacher(char *name, int age, int salary);void display();
private:int m_salary;
};
Teacher::Teacher(char *name, int age, int salary): People(name, age), m_salary(salary){}
void Teacher::display(){cout<<m_name<<"今年"<<m_age<<"岁了,是一名教师,每月有"<<m_salary<<"元的收入。"<<endl;
}int main(){People *p = new People("王志刚", 23);p -> display();p = new Teacher("赵宏佳", 45, 8200);p -> display();return 0;
}

运行结果:
王志刚今年23岁了,是个无业游民。
赵宏佳今年45岁了,是个无业游民。

我们直观认为,若指针指向了派生类对象,就应该使用派生类的成员变量和成员函数,但上例结果表明不对。
换句话:通过基类指针只能访问派生类的成员函数,不能访问派生类的成员函数。
解决:让基类指针能访问派生类的成员函数。增加虚函数。使用虚函数非常简单,只需要在函数声明前面增加 virtual 关键字。#include <iostream>
using namespace std;
//基类People
class People{
public:People(char *name, int age);virtual void display();  //声明为虚函数
protected:char *m_name;int m_age;
};
People::People(char *name, int age): m_name(name), m_age(age){}
void People::display(){cout<<m_name<<"今年"<<m_age<<"岁了,是个无业游民。"<<endl;
}
//派生类Teacher
class Teacher: public People{
public:Teacher(char *name, int age, int salary);virtual void display();  //声明为虚函数
private:int m_salary;
};
Teacher::Teacher(char *name, int age, int salary): People(name, age), m_salary(salary){}
void Teacher::display(){cout<<m_name<<"今年"<<m_age<<"岁了,是一名教师,每月有"<<m_salary<<"元的收入。"<<endl;
}
int main(){People *p = new People("王志刚", 23);p -> display();p = new Teacher("赵宏佳", 45, 8200);p -> display();return 0;
}

运行结果:
王志刚今年23岁了,是个无业游民。
赵宏佳今年45岁了,是一名教师,每月有8200元的收入。

有了虚函数,基类指针指向基类对象就使用基类的成员(包括成员变量和成员函数),指向派生类对象时就使用派生类的成员。
换句话,基类指针可以按照基类的方式来做事,也可按照派生类的方式做事,有多种形态,称为多态。
引用实现多态:
int main(){People p("王志刚", 23);Teacher t("赵宏佳", 45, 8200);People &rp = p;People &rt = t;rp.display();rt.display();return 0;
}

运行结果:
王志刚今年23岁了,是个无业游民。
赵宏佳今年45岁了,是一名教师,每月有8200元的收入。

构成多态条件:
1.必须存在继承关系
2.继承关系中必须有同名的虚函数,并且它们是覆盖关系
3.存在基类的指针,通过该指针调用虚函数。
构造函数,不能是虚函数,因为派生类不能继承基类的构造函数。
析构可以是虚函数。而且有时候必须声明为虚函数。
例:
#include <iostream>
using namespace std;
//基类
class Base{
public:Base();~Base();
protected:char *str;
};
Base::Base(){str = new char[100];cout<<"Base constructor"<<endl;
}
Base::~Base(){delete[] str;cout<<"Base destructor"<<endl;
}
//派生类
class Derived: public Base{
public:Derived();~Derived();
private:char *name;
};
Derived::Derived(){name = new char[100];cout<<"Derived constructor"<<endl;
}
Derived::~Derived(){delete[] name;cout<<"Derived destructor"<<endl;
}
int main(){Base *pb = new Derived();delete pb;cout<<"-------------------"<<endl;Derived *pd = new Derived();delete pd;return 0;
}

运行结果:
Base constructor
Derived constructor
Base destructor

Base constructor
Derived constructor
Derived destructor
Base destructor

本例中,不调用派生类的析构函数会导致name指向的100char类型的内存空间得不到释放。
1.为啥delete pb,不会调用调用派生类的析构函数:
这里的析构函数是非虚函数,通过指针访问非虚函数时,会根据指针的类型来确定要调用的函数;也就是说,指针指向哪个类就调用哪个类的函数,pb是基类的指针,所以不管它指向基类的对象还是派生类的对象,始终调用基类的析构函数。2. 为什么delete pd;会同时调用派生类和基类的析构函数呢?
pd 是派生类的指针,编译器会根据它的类型匹配到派生类的析构函数,在执行派生类的析构函数的过程中,又会调用基类的析构函数。派生类析构函数始终会调用基类的析构函数,并且这个过程是隐式完成的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/15456.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IP数据云揭示高风险IP的来源地

在全球网络安全日临近之际&#xff0c;IP数据云揭示高风险IP的来源地。这些高风险IP以其潜在威胁和犯罪活动而闻名&#xff0c;已引起了全球范围内的关注。 根据IP数据云介绍&#xff0c;高风险IP的主要来源地是位于亚洲和东欧的国家其中包括俄罗斯、朝鲜和乌克兰等地。这些地区…

基于matlab使用深度学习进行图像类别分类(附源码)

一、前言 此示例演示如何使用预训练卷积神经网络 &#xff08;CNN&#xff09; 作为特征提取器来训练图像类别分类器。 卷积神经网络 &#xff08;CNN&#xff09; 是深度学习领域的一种强大的机器学习技术。CNN使用大量不同图像进行训练。从这些大型集合中&#xff0c;CNN可…

QT自定义工具条渐变背景颜色一例

使用样式定义&#xff1a; QWidget* toolbar new QWidget(this);toolbar->setObjectName("main_tool");toolbar->setStyleSheet("#main_tool{background: qlineargradient(x1:0 , y1:0 , x2:1 , y2:0,""stop:0 rgba(0,255,0, 0.2),"&q…

5道Mysql面试题

1.什么Mysql的事务&#xff1f;事务的四大特性&#xff1f; Mysql中事务的隔离级别分为四大等级&#xff1a;读未提交&#xff08;READ UNCOMMITTED&#xff09;、读提交 &#xff08;READ COMMITTED&#xff09;、可重复读 &#xff08;REPEATABLE READ&#xff09;、串行化 …

多元回归预测 | Matlab基于逻辑回归(Logistic Regression)的数据回归预测,多输入单输出模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab基于逻辑回归(Logistic Regression)的数据回归预测,多输入单输出模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %% 清空环境变量…

【开放麒麟】VMware + openKylin 尝鲜“开放麒麟1.0”

7月5日&#xff0c;我国首个开源桌面操作系统“开放麒麟1.0”正式发布&#xff0c;标志着我国拥有了操作系统组件自主选型、操作系统独立构建的能力&#xff0c;填补了我国在这一领域的空白。 本期分享通过虚拟机 Vmware 安装 openKylin 操作系统的方式&#xff0c;来尝鲜一下…

【码银送书第二期】《高并发架构实战:从需求分析到系统设计》

很多软件工程师的职业规划是成为架构师&#xff0c;但是要成为架构师很多时候要求先有架构设计经验&#xff0c;而不做架构师又怎么会有架构设计经验呢&#xff1f;那么要如何获得架构设计经验呢&#xff1f; 一方面可以通过工作来学习&#xff0c;观察所在团队的架构师是如何…

2023通感一体化系统架构与关键技术白皮书

1 通感一体化业务与性能指标 1.1 通感一体化业务分类 根据通信与感知的相互关系 通信辅助感知类业务&#xff1a;通信的参考信号作为感知信号&#xff0c;实现目标定位、测速、手势识别等业务——高速可靠的通信能力为感知数据的汇聚提供保障&#xff0c;能够进一步提高感知精…

python接口自动化(二十四)--unittest断言——中(详解)

简介 上一篇通过简单的案例给小伙伴们介绍了一下unittest断言&#xff0c;这篇我们将通过结合和围绕实际的工作来进行unittest的断言。这里以获取城市天气预报的接口为例&#xff0c;设计了 2 个用例&#xff0c;一个是查询北京的天气&#xff0c;一个是查询 南京为例&#xf…

计网之体系结构(一)

计网之体系结构&#xff08;一&#xff09; 计算机网络概述计算机网络的概念计算机网络的功能计算机网络的发展计算机网络发展第一阶段计算机网络发展第二阶段&#xff08;三级结构&#xff09;计算机网络发展第三阶段&#xff08;多层次ISP结构&#xff09; 概念&#xff0c;功…

阿里云服务器架构X86计算、ARM、GPU/FPGA、裸金属和超级计算集群

阿里云服务器架构有什么区别&#xff1f;X86计算、ARM计算、GPU/FPGA/ASIC、弹性裸金属服务器、超级计算集群有什么区别&#xff1f;阿里云服务器网分享云服务器ECS架构详细说明&#xff1a; 目录 阿里云服务器ECS架构说明 X86计算 ARM计算 GPU/FPGA/ASIC 弹性裸金属服务…

Windows平台软件工程关键路径PDM图

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天来聊聊Windows平台下软件工程实践中涉及关键路径问题时常用的PDM图。 PDM图 我们用工程实例来完整讲解PDM图的节点表示&#xff0c; ES(最早开始时间) 、 LS(最迟开始时间)、EF(最早完成时间)、LF(最迟…