• 21-30==c++知识点


    21、C++与Java的区别
    • (1)语言特性:

      • Java给开发人员提供了更为简洁的语法;完全面向对象,由于JVM可以安装到任何的操作系统上,所以说它的可移植性强
      • Java语言中没有指针的概念,引入了真正的数组。不同于C++中利用指针实现的“伪数组”,Java引入了真正的数组,同时将容易造成麻烦的指针从语言中去掉,这将有利于防止在C++程序中常见的因为数组操作越界等指针操作而对系统数据进行非法读写带来的不安全问题
      • C++也可以在其他系统运行,但是需要不同的编码(这一点不如Java,只编写一次代码,到处运行),例如对一个数字,在windows下是大端存储,在unix中则为小端存储。Java程序一般都是生成字节码,在JVM里面运行得到结果。
      • Java用接口(Interface)技术取代C++程序中的多继承性。接口与多继承有同样的功能,但是省却了多继承在实现和维护上的复杂性
    • (2)垃圾回收

      • c++用析构函数回收垃圾,写c和c++程序时一定要注意内存的申请和释放
      • Java语言不使用指针,内存的分配和回收都是自动进行的,程序员无须考虑内存碎片的问题
    • (3)应用场景

      • Java在桌面程序上不如c++实用,C++可以直接编译成exe文件,指针是c++的优势,可以直接对内存的操作,但同时具有危险性 。(操作内存的确是一项非常危险的事情,一旦指针指向的位置发生错误,或者误删除了内存中某个地址单元存放的重要数据,后果是可想而知的)
      • Java在Web 应用上具有C++ 无可比拟的优势,具有丰富多样的框架
      • 对于底层程序的编程以及控制方面的编程,C++很灵活,因为有句柄(管理对象的地址,管理指针的指针)的存在
    22、C++中struct和class的区别
    • (1)相同点:
      • 两者都拥有成员函数、公有私有部分。
      • 任何可以使用class完成的工作,同样可以使用struct完成
    • (2)不同点:
      • struct默认公有,class默认私有
      • class默认是private继承,而struct模式是public继承
    • (3)引申:C++和C的struct区别
      • C语言中:struct是用户自定义数据类型(UDT);C++中struct是抽象数据类型(ADT),支持成员函数的定义,(C++中的struct能继承,能实现多态)
      • C中struct是没有权限的设置的,且struct中只能是一些变量的集合体,可以封装数据却不可以隐藏数据,而且成员不可以是函数
      • C++中,struct增加了访问权限,且可以和类一样有成员函数,成员默认访问说明符为public(为了与C兼容)
      • struct作为类的一种特例是用来自定义数据结构的。一个结构标记声明后,在C中必须在结构标记前加上struct,才能做结构类型名(除:typedef struct class{};);C++中结构体标记(结构体名)可以直接作为结构体类型名使用,此外结构体struct在C++中被当作类的一种特例
    23、define宏定义和const的区别
    • (1)编译阶段

      • define是在编译的预处理阶段起作用,而const是在编译、运行的时候起作用
    • (2)安全性

      • define只做替换,不做类型检查和计算,也不求解,容易产生错误,一般最好加上一个大括号包含住全部的内容,要不然很容易出错
      • const常量有数据类型,编译器可以对其进行类型安全检查
    • (3)内存占用

      • define只是将宏名称进行替换,在内存中会产生多分相同的备const在程序运行中只有一份备份,且可以执行常量折叠,能将复杂的的表达式计算出结果放入常量表
      • 宏替换发生在编译阶段之前,属于文本插入替换;const作用发生在编译过程中
      • 宏替换不进行编译器的类型检查,但const会检查数据类型
      • 宏定义的数据没有分配内存空间,只是插入替换掉const定义的变量只是值不能改变,但要分配内存空间
    24、C++中const和static的作用
    • (1)static
      • 不考虑类的情况
        • 隐藏。所有不加static的全局变量和函数具有全局可见性,可以在其他文件中使用,加了之后只能在该文件所在的编译模块中使用
        • c++默认初始化为0,包括未初始化的全局静态变量与局部静态变量,都存在全局未初始化区
        • 静态变量在函数内定义,始终存在,且只进行一次初始化,具有记忆性,其作用范围与局部变量相同,函数退出后仍然存在,但不能使用
      • 考虑的类的情况
        • static成员变量:只与类关联,不与类的对象关联。定义时要分配空间,不能在类声明中初始化,必须在类定义体外部初始化,初始化时不需要标示为static;可以被非static成员函数任意访问。
        • **static成员函数:不具有this指针,**无法访问类对象的非static成员变量和非static成员函数;不能被声明为为const、虚函数和volatile;可以被非static成员函数任意访问
    • (2)const
      • 不考虑类的情况
        • const常量在定义时必须初始化,之后无法更改
        • const形参可以接收const和非const类型的实参,例如
          //i可以是int型或者const int型
          void fun(const int&i){
          	//...
          }
          
          • 1
          • 2
          • 3
          • 4
      • 考虑类的情况
        • const成员变量:不能在类定义外部初始化,只能通过构造函数初始化列表进行初始化
        • const成员函数:const对象不可以调用非const成员函数;非const对象都可以调用;不可以改变非mutable(用该关键字声明的变量可以在const成员函数中被修改)数据的值
    25、C++的顶层const和底层const
    • (1)概念区分

      • 顶层const:指的是const修饰的变量本身是一个常量,无法修改,指的是指针,就是*号的右边
      • 底层const:指的是const修饰的变量所指向的对象是一个常量,指的是所指变量,就是*号的左边
    • (2)example

      int a=10;
      int* const b1=&a;//顶层const,b1本身是一个常量
      const int* b2=&a;//底层const,b2本身可变,所指的对象是常量
      const int b3=20;//顶层const,b3是常量不可变
      const int* const b4=&a;//前一个const为底层,后一个为顶层,b4不可变
      const int& b5=a;//用于声明引用变量,都是底层const
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • (3)区分作用

      • 执行对象拷贝时有限制,常量的底层const不能赋值给非常量的底层const
    int num_c=3;
    const int* p_c=&num_c;//p_c为底层const的指针
    //int *p_d = p_c;  //错误,不能将底层const指针赋值给非底层const指针 
    const int *p_d = p_c; //正确,可以将底层const指针复制给底层const指针
    
    • 1
    • 2
    • 3
    • 4
    - 使用命名的强制类型转换函数const_cast时,只能改变运算对象的底层const
    
    • 1
    int num_e = 4;  
    const int *p_e = &num_e;  
    //*p_e = 5;  //错误,不能改变底层const指针指向的内容  
    int *p_t=const_cast<int*>(p_e);//正确,const_cast可以改变运算对象的底层const。但是使用时一定要知道num_e不是const的类型。
    *p_f=5;//正确,非顶层const指针可以改变指向的内容
    cout<<num_e<<endl;; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    26、类的对象存储空间?
    • 空的类是会占用内存空间的,而且大小是1,原因是C++要求每个实例在内存中都有独一无二的地址。
    • 类内部的成员变量:
      普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。
      static修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区。
    • 类内部的成员函数:
      普通函数:不占用内存。
      虚函数:要占用4个以上字节,用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的。
    • 准则:
      • 空类占用1个字--------------------------------------------------------1
      • 静态成员变量不占内存大小----------------------------------------0
      • 普通成员函数不占内存大小----------------------------------------0
      • 只要含有虚函数(继承的或者是自定义)的占内存---------1个字(不是字节,是根据你机器位数变化的)
      • 所有的非静态成员变量---------------------------------------------本身类定义的成员变量总和+父类继承下来的所有变量总和
      • 注意字节对齐原则(整字数)
      • 子类内存大小=父类内存大小+子类自定义的成员变量大小
      • 一个类中不管有多少个虚函数,虚函数表指针永远只有一个(只占一个字),这个指针是虚函数地址数组的首地址。数组元素是各个虚函数的地址,因此只需要知道这个首地址,那么就可以找到所有的虚函数。
        详细看类内存大小
    27、final和override关键字
    • override(重写)
      当在父类中使用了虚函数时候,你可能需要在某个子类中对这个函数进行重写,以下方法都可以:
      class A
      {
      	virtual void foo();
      }
      class B:public A
      {
       	void foo();//OK
       	virtual void foo();//OK
       	void foo() override;//OK
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    • 目的:如果不使用override,当你手一抖,将foo()写成了f00()会怎么样呢?结果是编译器并不会报错,因为它并不知道你的目的是重写虚函数,而是把它当成了新的函数。如果这个虚函数很重要的话,那就会对整个程序不利。所以,override的作用就出来了,它指定了子类的这个虚函数是重写的父类的,如果你名字不小心打错了的话,编译器是不会编译通过的:
      class A
      {
      	virtual void foo();
      }
      class B:public A
      {
      	virtual void f00(); //OK,这个函数是B新增的,不是继承的
      	virtual void f0o() override;//Error, 加了override之后,这个函数一定是继承自A的,A找不到就报错
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • final
      当不希望某个类被继承,或不希望某个虚函数被重写,可以在类名和虚函数后添加final关键字,添加final关键字后被继承或重写,编译器会报错。例子如下:
      class Base
      {
      	virtual void foo();
      }
      class A:public Base
      {
      	void foo() final; foo 被override并且是最后一个override,在其子类中不可以重写
      }
      class B final:A// 指明B是不可以被继承的
      {
      	void foo() override; // Error: 在A中已经被final了
      }
      class C:B // Error: B is final
      {
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    28、拷贝初始化和直接初始化
    • 当用于类类型对象时,初始化的拷贝形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,拷贝初始化总是调用拷贝构造函数。拷贝初始化首先使用指定构造函数创建一个临时对象,然后用拷贝构造函数将那个临时对象拷贝到正在创建的对象,for example as follow:
    /*C++中的直接初始化指的是直接调用类的构造函数进行初始化,如下例如*/
    string a; //调用默认构造函数
    string a("hello"); //调用参数为const char *类型的构造函数
    string b(a); //调用拷贝构造函数
    
    /*复制初始化指的是用“=”号来初始化对象,例如*/
    string a="hello";//相当于隐式调用构造函数
    string b=a;//注意这里相当于隐式调用拷贝构造函数,而不是调用赋值运算符函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 为了提高效率,允许编译器跳过创建临时对象这一步,直接调用构造函数构造要创建的对象,这样就完全等价于直接初始化了(直接初始化与复制初始化等价),但是需要辨别两种情况。
      • 当拷贝构造函数为private时:复制初始化会报错
      • 使用explicit修饰构造函数时:如果构造函数存在隐式转换(是指不需要用户干预,编译器私下进行的类型转换行为),编译时会报错
    29、初始化和赋值 的区别
    • 对于简单类型来说,初始化和赋值没什么区别
    • 对于类和复杂数据类型来说,这两者的区别就大了,举例如下:
      class A{
      public:
          int num1;
          int num2;
      public:
          //A(int a=0,int b=0){num1=a;num2=b;}
          A(int a=0, int b=0):num1(a),num2(b){};//构造参数列表实现
          A(const A& a){};
          //重载 = 号操作符函数
          A& operator=(const A& a){
              num1 = a.num1 + 1;
              num2 = a.num2 + 1;
              return *this;
         };
      };
      int main(){
          A a(1,1);
          A a1=a;//拷贝初始化操作,调用拷贝构造函数
          A b;
          b=a;//赋值操作,对象a中,num1 = 1,num2 = 1;对象b中,num1 = 2,num2 = 2
          return 0;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
    30、extern"C"的用法
    • 为了能够正确的在C++代码中调用C语言的代码:在程序中加上extern"C"后,相当于告诉编译器这部分代码是C语言写的,因此要按照C语言进行编译,而不是C++;
    • 哪些情况下使用extern “C”:
      • (1)C++代码中调用C语言代码;
      • (2)在C++中的头文件中使用;
      • (3)在多个人协同开发时,可能有人擅长C语言,而有人擅长C++;
    • 举个例子,C++中调用C代码:
      #ifndef __MY_HANDLE_H__
      #define __MY_HANDLE_H__
      extern "C"{
      	typedef unsigned int result_t;
      	typedef void* my_handle_t;
      	
      	my_handle_t create_handle(const char* name);
      	result_t operte_on_handle(my_handle_t handle);
      	void close_handle(my_handle_t handle);
      }
      #endif//__MY_HANDLE_H__
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    • 综上,总结出使用方法,在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。所以使用extern "C"全部都放在于cpp程序相关文件或其头文件中。
    /*========c++调用c函数*/
    //xx.h
    extern int add(...)
    //xx.c
    int add(){}
    //xx.cpp
    extern "C"{#include "xx.h}
    
    /*========c调用c++函数*/
    //xxx.h
    extern "C"{int add()}
    //xxx.cpp
    int add(){}
    //xxx.c
    extern int add();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    gnss rtcm rtklib Ntrip...
    java:SPI使用
    如何用BigDecimal定义数字
    HTML表格学习
    基于SpringBoot的民宿在线预定平台
    (动手学习深度学习)第13章 实战kaggle竞赛:CIFAR-10
    做大数据开发一定要知道几个重要的框架和工具及知识点
    申请专利必须把技术公开吗?
    Linux用户
    基于javaweb的crm客户关系管理系统(java+springboot+mysql)
  • 原文地址:https://blog.csdn.net/weixin_47397155/article/details/126346015