c++查漏补缺

news2024/11/14 22:00:14
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.coloradmin.cn/o/731811.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【剑指offer】8. 斐波那契数列(java)

文章目录 斐波那契数列描述输入描述&#xff1a;返回值描述&#xff1a;示例1示例2示例3思路非递归递归 完整代码 斐波那契数列 描述 大家都知道斐波那契数列&#xff0c;现在要求输入一个正整数 n &#xff0c;请你输出斐波那契数列的第 n 项。 斐波那契数列是一个满足 f …

PHP学生工作平台管理系统mysql数据库web结构apache计算机软件工程网页wamp

一、源码特点 PHP学生工作平台管理系统 是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为PHP APACHE&#xff0c;数据 库为mysql5.0&#xff0c;使用php语言开发…

linux 如何挂载fat32格式u盘,如何挂载NTFS 文件系统的硬盘

linux系统默认可以识别fat32u盘&#xff0c;对ntfs格式u盘不能识别 具体挂载方式如下 1、插入u盘 2、mkdir /mnt/usb 此命令用于创建挂载u盘的目录&#xff0c;只需创建一次就可以&#xff0c;若已经存在则不需要再次创建 3、fdisk -l 找到u盘路径 上图显示的sdb1,sdb2,sdb5…

Gradio,我们可以为我们的模型创建Web界面

Gradio是一个Python库&#xff0c;允许我们快速为机器学习模型创建可定制的接口。 使用Gradio&#xff0c;我们可以为我们的模型创建Web界面&#xff0c;而无需编写任何HTML&#xff0c;CSS或JavaScript。 Gradio旨在与广泛的机器学习框架配合使用&#xff0c;包括TensorFlow&a…

IOU发展历程学习记录

概述 IOU的出现主要最先运用在预测bbox框和target bbox框之间的重叠问题&#xff0c;为NMS提供相应的数值支撑。另外在bbox框的回归问题上&#xff0c;由于L1 Loss存在如下问题&#xff1a;当损失函数对x的导数为常数&#xff0c;在训练后期&#xff0c;x很小时&#xff0c;若…

GEE:基于MODIS土地覆盖类型“混交林”的净初级生产力(NPP)的区域统计

作者:CSDN @ _养乐多_ 本文将介绍如何使用Google Earth Engine(GEE)平台提取特定地区的净初级生产力(NPP)的统计信息,并在地图上可视化。通过加载MODIS数据集,并使用GEE提供的函数和方法,能够高效地计算特定地区的净初级生产力的平均值。 文章目录 一、代码详解二、代…

大模型的数据供血系统-向量数据库常识科普

1. 数据库行业有了新动向 对于传统数据库研发运维来说&#xff0c;数据库行业上次有概念创新&#xff0c;还是十几年前的NoSQL…… 在AI大行业发展的推进下&#xff0c;向量数据库成为了最新兴的数据库技术趋势&#xff0c;业内多家开源向量数据库都拿到了高额融资&#xff0c;…

《网络是怎样连接的》-户根勤

第一章&#xff1a;浏览器生成消息-探索浏览器内部 主要讲HTTP消息、DNS和委托协议栈发送消息。 第二章&#xff1a;用电信号传输TCP/IP数据-探索协议栈和网卡 主要讲套接字的创建、连接、通信、断开和删除四个阶段&#xff1b;IP与以太网的包收发阶段&#xff1b;UDP协议的收…

使用LocalThread获取当前线程的用户ID错误

说明&#xff1a;LocalThread是线程变量&#xff0c;可以往该线程变量中填充我们项目用户的ID&#xff0c;可以在其他的业务代码中直接获取&#xff0c;十分方便&#xff0c;详细参考&#xff1a;http://t.csdn.cn/k75rs LocalThread使用 第一步&#xff1a;创建类 创建一个…

北京市自动驾驶出行服务商业化试点启动,无人驾驶会是未来吗?

北京市高级级别自动驾驶示范区工作办公室公告称&#xff0c;智能网联乘用车“车内无人”商业化试点正式启动。根据最新修订的《北京市智能网联汽车政策先行区自动驾驶出行服务商业化试点管理细则&#xff08;试行&#xff09;》&#xff0c;企业在满足相关要求后&#xff0c;可…

如何用https协议支持小程序

步骤一&#xff1a;下载SSL证书 登录数字证书管理服务控制台。在左侧导航栏&#xff0c;单击SSL 证书。在SSL证书页面&#xff0c;定位到目标证书&#xff0c;在操作列&#xff0c;单击下载。 在服务器类型为Nginx的操作列&#xff0c;单击下载。 解压缩已下载的SSL证书压缩…

English Learning - L3 作业打卡 Lesson8 Day58 2023.7.3 周一

English Learning - L3 作业打卡 Lesson8 Day58 2023.7.3 周一 引言&#x1f349;句1: And this is when I learned that our borders and our obstacles can only do two things: one, stop us in our tracks or two, force us to get creative.成分划分弱读连读爆破语调 &…

无线基站与无线频谱资源

文章目录 基站的主要组成天线馈线&#xff08;电缆线&#xff09;RRU&#xff08;射频拉远单元&#xff0c;Remote Radio Unit&#xff09;BBU&#xff08;室内基带处理单元&#xff0c;Building Base band Unit&#xff09;AAU&#xff08;有源天线单元&#xff0c;Active Ant…

Summer test

目录 第一个只出现一次的字符判定字符是否唯一 第一个只出现一次的字符 原题链接&#xff1a;第一个只出现一次的字符 int FirstNotRepeatingChar(char* str ) {int arr[200] {0};int len strlen(str);int i0;for(i0;i<len;i){arr[str[i]];}for(i0;i<len;i){if(arr[s…

[ABC218G] Game on Tree 2 树上游戏

[ABC218G] Game on Tree 2 树上游戏 文章目录 [ABC218G] Game on Tree 2 树上游戏题面翻译输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 样例 #3样例输入 #3样例输出 #3 题目大意分析水法code 正解code 题面翻译 给定一棵树&#xff0c;以及…

leetcode 106. 从中序与后序遍历序列构造二叉树

2023.7.8 让我很难受的一道题&#xff0c;个人感觉难度不止中等。 首先要知道的是知道了前序/后序 中序 之后&#xff0c;是可以构造出相应且唯一的二叉树的。 本道题的思路通过递归的方式根据中序遍历数组和后序遍历数组构建二叉树&#xff0c;并返回根节点。递归的结束条…

【通览一百个大模型】Anthropic LLM(Anthropic)

【通览一百个大模型】Anthropic LLM&#xff08;Anthropic&#xff09; 作者&#xff1a;王嘉宁&#xff0c;本文章内容为原创&#xff0c;仓库链接&#xff1a;https://github.com/wjn1996/LLMs-NLP-Algo 订阅专栏【大模型&NLP&算法】可获得博主多年积累的全部NLP、大…

Ubuntu安装VMtools实现与主机之间复制粘贴

目录 一、安装 VMware Tools 二、Ubuntu命令 一、安装 VMware Tools 右键点击你创建的系统&#xff0c;然后出现菜单下滑找到安装 VMware Tools&#xff08;T&#xff09; 这个点击安装&#xff1b; 右键点击你创建的系统&#xff0c;然后出现菜单下滑找到设置; 然后弹出虚…

USB转串口那些事儿—电源与防倒灌设计

USB转串口芯片和串口负载&#xff08;MCU、CPU、其他串口外设等&#xff09;的供电方式可以分为2个大类&#xff1a;统一供电和独立供电。 一、供电说明 统一供电是指USB芯片和串口负载使用同一电源&#xff0c;上下电同步&#xff0c;此时不会存在彼此之间电流倒灌的问题。 …

【异常错误】Unexpected option: --local_rank=0(pycharm可以run但是不可以debug)

今天在使用用run运行shell文件转为的cmd命令后&#xff0c;run可以正常运行&#xff0c;但是debug却出现问题&#xff0c;错误信息&#xff1a; Usage:pydevd.py --port N [(--client hostname) | --server] --file executable [file_options] Traceback (most recent call la…