C++面向对象(二)

news2025/6/4 22:42:39

面向对象基础内容参考:

C++面向对象(一)-CSDN博客 

友元函数

类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,如下所示:

class Box
{
   double width;
public:
   double length;
   friend void printWidth( Box box );
   void setWidth( double wid );
};

声明类 ClassTwo 的所有成员函数作为类 ClassOne 的友元,需要在类 ClassOne 的定义中放置如下声明:

friend class ClassTwo;

请看下面的程序:

#include <iostream>
 
using namespace std;
 
class Box
{
   double width;
public:
   friend void printWidth( Box box );
   void setWidth( double wid );
};
 
// 成员函数定义
void Box::setWidth( double wid )
{
    width = wid;
}
 
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
   /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
   cout << "Width of box : " << box.width <<endl;
}
 
// 程序的主函数
int main( )
{
   Box box;
 
   // 使用成员函数设置宽度
   box.setWidth(10.0);
   
   // 使用友元函数输出宽度
   printWidth( box );
 
   return 0;
}

为什么友元函数需要在类内声明?(即使它不属于类)

友元函数(friend function)确实不属于任何类,它只是一个普通函数其他类的成员函数,但拥有访问当前类的 private 和 protected 成员的权限。

友元函数的声明必须放在类内部,原因如下:

授予访问权限

C++ 的访问控制(private/protected)默认禁止外部函数访问类的非公开成员。

通过在类内使用 friend 声明,显式告诉编译器:“这个函数(即使不是我的成员)可以访问我的私有成员。”

语法要求

C++ 标准规定,friend 声明必须出现在类的定义中,否则编译器无法知道哪些外部函数有特殊访问权限。

避免全局污染

如果友元函数不需要类内声明,那么任何外部函数都可以随意访问类的私有成员,破坏封装性。通过类内 friend 声明,只有被明确指定的函数才能获得访问权。

this 指针

在 C++ 中,this 指针是一个特殊的指针,它指向当前对象的实例。

在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。

this是一个隐藏的指针,可以在类的成员函数中使用,它可以用来指向调用对象。

当一个对象的成员函数被调用时,编译器会隐式地传递该对象的地址作为 this 指针。

友元函数没有 this 指针,因为友元不是类的成员,只有成员函数才有 this 指针。

下面的实例有助于更好地理解 this 指针的概念:

#include <iostream>
 
class MyClass {
private:
    int value;
 
public:
    void setValue(int value) {
        this->value = value;
    }
 
    void printValue() {
        std::cout << "Value: " << this->value << std::endl;
    }
};
 
int main() {
    MyClass obj;
    obj.setValue(42);
    obj.printValue();
 
    return 0;
}

实例解析:

  • 以上实例中,我们定义了一个名为 MyClass 的类,它有一个私有成员变量 value。

  • 类中的 setValue() 函数用于设置 value的 值,而 printValue() 函数用于打印 value 的值。

  • 在 setValue() 函数中,我们使用 this 指针来引用当前对象的成员变量 value,并将传入的值赋给它,这样可以明确地告诉编译器我们想要访问当前对象的成员变量,而不是函数参数或局部变量。

  • 在 printValue() 函数中,我们同样使用 this 指针来引用当前对象的成员变量 value,并将其打印出来。

  • 在 main() 函数中,我们创建了一个 MyClass 的对象 obj,然后使用 setValue() 函数设置 value 的值为 42,并通过 printValue() 函数打印出来。

  • 通过使用 this 指针,我们可以在成员函数中访问当前对象的成员变量,即使它们与函数参数或局部变量同名,这样可以避免命名冲突,并确保我们访问的是正确的变量。

指向类的指针

类比结构体指针。 

一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 ->,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化。

在 C++ 中,指向类的指针指向一个类的对象,与普通的指针相似,指向类的指针可以用于访问对象的成员变量和成员函数。

声明和初始化指向类的指针

#include <iostream>

class MyClass {
public:
    int data;

    void display() {
        std::cout << "Data: " << data << std::endl;
    }
};

int main() {
    // 创建类对象
    MyClass obj;
    obj.data = 42;

    // 声明和初始化指向类的指针
    MyClass *ptr = &obj;

    // 通过指针访问成员变量
    std::cout << "Data via pointer: " << ptr->data << std::endl;

    // 通过指针调用成员函数
    ptr->display();

    return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Data via pointer: 42 
Data: 42

动态分配内存

指向类的指针还可以用于动态分配内存,创建类的对象:

#include <iostream>

class MyClass {
public:
    int data;

    void display() {
        std::cout << "Data: " << data << std::endl;
    }
};

int main() {
    // 动态分配内存创建类对象
    MyClass *ptr = new MyClass;
    ptr->data = 42;

    // 通过指针调用成员函数
    ptr->display();

    // 释放动态分配的内存
    delete ptr;

    return 0;
}

指向类的指针可以作为函数参数传递

#include <iostream>

class MyClass {
public:
    int data;

    void display() {
        std::cout << "Data: " << data << std::endl;
    }
};

// 函数接受指向类的指针作为参数
void processObject(MyClass *ptr) {
    ptr->display();
}

int main() {
    MyClass obj;
    obj.data = 42;

    // 将指向类的指针传递给函数
    processObject(&obj);

    return 0;
}

其实就和结构体指针用法类似。

特点

  • 内存分配:在堆(heap)上分配,需手动管理内存。

  • 生命周期:直到显式调用 delete 时才会销毁。

  • 优点

    • 堆空间大(受系统内存限制),适合大对象。

    • 生命周期可灵活控制。

  • 缺点

    • 必须手动 delete,否则内存泄漏。

    • 忘记释放会导致资源泄露,多次释放会崩溃。

其实new的时候,就相当于c里面给结构体指针malloc了一块地址空间。

C++定义类指针的时候会为类分配地址空间吗

在 C++ 中,定义类指针时不会自动为类对象分配内存空间。指针本身只是一个存储地址的变量,它需要明确指向一个有效的对象地址才能安全使用。以下是详细分析:

指针定义与内存分配的关系

(1) 仅定义指针(未初始化)

MyClass* ptr;  // 只分配了指针本身的内存(通常4/8字节),未分配类对象的内存

指针变量 ptr: 编译器会为指针分配内存(32位系统4字节,64位系统8字节),用于存储地址。

类对象 MyClass未被分配内存,此时 ptr 是“野指针”(指向随机地址),访问会导致未定义行为(崩溃或数据错误)。

(2) 初始化指针后

MyClass* ptr = new MyClass();  // 动态分配类对象内存,ptr存储其地址

new 关键字: 在堆(Heap)上为 MyClass 对象分配内存,并调用构造函数。

ptr 的作用: 存储该对象的地址,通过 ptr->member 访问对象成员。

内存分配方式对比

操作类对象内存分配位置生命周期管理示例
动态分配(new)堆(Heap)需手动 deleteMyClass* p = new MyClass;
栈对象(自动变量)栈(Stack)作用域结束自动销毁MyClass obj;
智能指针自动管理auto p = make_shared<MyClass>();

常见错误示例

(1) 野指针(未初始化)

MyClass* ptr;
ptr->doSomething();  // 崩溃!ptr未指向有效对象

(2) 内存泄漏(未释放)

MyClass* ptr = new MyClass();// 忘记 delete,对象内存永远泄漏

(3) 悬空指针(Dangling Pointer)

MyClass* ptr = new MyClass();delete ptr;         // 释放内存
ptr->doSomething(); // 危险!ptr仍存储旧地址

安全实践建议

(1) 初始化时分配对象

MyClass* ptr = new MyClass();  // 分配内存 + 构造
// 使用后...
delete ptr;  // 释放内存 + 析构

(2) 使用智能指针(C++11起推荐)

include <memory>
std::unique_ptr<MyClass ptr = std::make_unique<MyClass();  // 自动释放

(3) 优先使用栈对象(无需手动管理)

MyClass obj;  // 自动分配在栈上,作用域结束销毁

底层原理

指针的本质: 指针变量存储的是内存地址(如 0x7ffd1234),其大小与系统架构相关(与指向的类型无关)。

对象内存分配

new 操作符调用 operator new 分配内存,然后调用构造函数。

对象的内存大小由类成员变量和编译器填充决定(可通过 sizeof(MyClass) 获取)。

类的静态成员

我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。

静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化,如下面的实例所示。

下面的实例有助于更好地理解静态成员数据的概念:

#include <iostream>
 
using namespace std;
 
class Box
{
   public:
      static int objectCount;
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // 每次创建对象时增加 1
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
   private:
      double length;     // 长度
      double breadth;    // 宽度
      double height;     // 高度
};
 
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
 
int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // 声明 box1
   Box Box2(8.5, 6.0, 2.0);    // 声明 box2
 
   // 输出对象的总数
   cout << "Total objects: " << Box::objectCount << endl;
 
   return 0;
}

静态成员函数

如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。

静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。

静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。

静态成员函数与普通成员函数的区别:

  • 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。

  • 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。

下面的实例有助于更好地理解静态成员函数的概念:

#include <iostream>
 
using namespace std;
 
class Box
{
   public:
      static int objectCount;
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // 每次创建对象时增加 1
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
      static int getCount()
      {
         return objectCount;
      }
   private:
      double length;     // 长度
      double breadth;    // 宽度
      double height;     // 高度
};
 
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
 
int main(void)
{
  
   // 在创建对象之前输出对象的总数
   cout << "Inital Stage Count: " << Box::getCount() << endl;
 
   Box Box1(3.3, 1.2, 1.5);    // 声明 box1
   Box Box2(8.5, 6.0, 2.0);    // 声明 box2
 
   // 在创建对象之后输出对象的总数
   cout << "Final Stage Count: " << Box::getCount() << endl;
 
   return 0;
}

C++ 类中的静态成员(static members)可以在不创建对象的情况下直接访问,因为它们属于类本身,而不是类的某个特定对象。

静态成员的访问方式:

公有静态成员(public static)

可以通过 类名::成员名 直接访问。

如果通过对象访问也可以,但不推荐(因为静态成员属于类,而非对象)。

私有静态成员(private static)

必须通过类的静态成员函数友元访问,不能直接通过 类名::成员名 访问(除非在类内部)。

更多待补充。

普通函数 

不要觉得C++里面全是成员函数,也有普通函数的,毕竟一个成员函数里除了调用各种其他的成员函数,肯定也要调用很多普通函数,比如strncpy/strcmp等等。 

在 C++ 中,普通函数(Free Functions / Non-member Functions) 是指不属于任何类的独立函数,它们定义在全局或命名空间作用域中,可以直接调用而无需通过对象。以下是关于普通函数的详细说明:

普通函数的特点

  • 不属于任何类:没有 this 指针,不依赖对象调用。

  • 作用域

    • 定义在全局作用域(全局函数)。

    • 或定义在命名空间内(避免命名冲突)。

  • 访问权限

    • 默认只能访问 public 成员(若操作类对象)。

    • 若需访问类的 private/protected 成员,需被声明为类的友元函数(friend

定义与调用示例
(1) 全局函数

#include <iostream>
// 全局普通函数
void printMessage() {
    std::cout << "Hello, World!" << std::endl;
}

int main() {
    printMessage(); // 直接调用
    return 0;
}

(2) 命名空间内的普通函数

namespace MyUtils {
    int add(int a, int b) { // 命名空间内的普通函数
        return a + b;
    }
}

int main() {
    int sum = MyUtils::add(3, 5); // 通过命名空间调用
    std::cout << "Sum: " << sum;   // 输出: Sum: 8
    return 0;
}

普通函数 vs 成员函数

特性普通函数成员函数
所属关系不属于任何类属于某个类
调用方式func(args)obj.memberFunc(args)
访问权限默认只能访问类的 public可访问类的 private/protected
this 指针有(指向当前对象)
适用场景工具函数、运算符重载等操作对象内部状态

普通函数与类的交互

(1) 通过参数操作对象

普通函数可通过对象的 public 接口操作数据:

class Circle {
    double radius;
public:
    Circle(double r) : radius(r) {}
    double getArea() const { return 3.14 * radius * radius; }
};

// 普通函数操作 Circle 对象
void printArea(const Circle& c) {
    std::cout << "Area: " << c.getArea(); // 调用 public 方法
}

友元函数访问私有成员

若需直接访问类的私有成员,需在类内声明为 friend

class BankAccount {
    double balance;
public:
    BankAccount(double b) : balance(b) {}
    friend void audit(const BankAccount& acc); // 友元声明
};

// 友元函数(仍是普通函数)
void audit(const BankAccount& acc) {
    std::cout << "Current balance: " << acc.balance; // 直接访问私有成员
}

为什么使用普通函数?

  • 解耦:将通用功能与类分离,减少类的复杂性。

  • 灵活性:可跨多个类复用(如数学计算、日志工具)。

  • 运算符重载:某些运算符必须通过普通函数重载(如 <<)。


总结

  • 普通函数是独立于类的函数,定义在全局或命名空间作用域。

  • 默认只能访问类的 public 成员,若需访问私有成员需声明为 friend

  • 常用于工具函数、运算符重载、工厂模式等场景。

  • 与成员函数相比,普通函数更适用于不直接操作对象内部状态的通用功能。

更多补充

linux应用层用什么语言来进行应用开发其实都可以,只要对应的语言环境能提供跟linux之间的交互接口即可,类似于要有个中间人,比如java的jvm虚拟机,python的解释器等等,C++也是同理,每种语言一般都是自己的库能用,要么是原生库要么是三方库,总之都有现成的库可以用,否则就发展不起来。我们做嵌入式开发时,底层都是用C语言,应用层可以用C语言,也可以用C++,用什么语言开发,通常都是以进程为单位,因为编译链接就是以进程为单位的,然后形成一个可执行程序,各种语言的编译链接方法不一样,所以,一般都不会在一个进程里面使用多种语言来进行开发。 

C++里面调用函数都需要使用对象来调用吗?

在C++中,是否需要使用对象来调用函数取决于函数的类型和定义方式。以下是对这一问题的详细分析:

非静态成员函数

常规调用方式:非静态成员函数是与类的实例(即对象)紧密相关的函数,它们通常需要通过对象来调用。这是因为非静态成员函数在执行时会隐式地接收一个指向调用它的对象的指针作为参数,该参数被称为this指针。例如,有一个类MyClass,其中定义了一个非静态成员函数myFunction,那么可以通过创建MyClass的对象obj,然后使用obj.myFunction()的方式来调用该函数。

指针或引用调用:除了直接使用对象调用外,还可以使用指向对象的指针或引用来调用非静态成员函数。比如,如果有一个指向MyClass对象的指针pObj,那么可以使用pObj-&gt;myFunction()来调用;如果是引用类型的变量refObj,则可以使用refObj.myFunction()来调用。

静态成员函数

无需对象即可调用:静态成员函数是属于整个类的函数,而不是属于某个特定对象的函数。因此,可以直接通过类名来调用静态成员函数,而不需要创建类的对象。例如,对于上述的MyClass类,如果其中定义了一个静态成员函数staticFunction,那么可以直接使用MyClass::staticFunction()来调用该函数。

通过对象或指针间接调用:虽然静态成员函数可以直接通过类名调用,但也可以通过对象或指向对象的指针来调用。不过,这种调用方式相对较少见,因为静态成员函数本身与对象无关,直接通过类名调用更加直观和符合逻辑。

普通全局函数

无需对象即可调用:普通全局函数是指在类之外定义的函数,它们不属于任何类,因此不需要使用对象来调用。只要在程序的作用域内声明了该函数,就可以直接通过函数名来调用它。例如,定义了一个全局函数globalFunction,那么可以在任何地方直接使用globalFunction()来调用该函数。

综上所述,C++中的函数调用是否需要使用对象取决于函数的类型和定义方式。对于非静态成员函数,通常需要通过对象来调用;对于静态成员函数和普通全局函数,则可以直接通过类名或函数名来调用。

嵌入式的应用层用C++开发和用C开发有什么区别?

嵌入式的应用层开发中,使用C语言和C++各有特点,具体区别如下:

开发范式

C语言:采用面向过程的编程范式,以函数为基本单位组织代码,程序的执行流程按照预先设定的函数调用顺序进行。这种方式在处理简单的逻辑和流程时非常直接和高效,但对于复杂的系统,代码的可读性和可维护性可能会随着系统规模的增大而降低。

C++:支持面向对象、泛型、过程化等多种编程范式。通过类和对象的概念,将数据和操作封装在一起,提高了代码的模块化和可维护性。同时,C++的模板机制允许编写通用的代码,适用于各种不同的数据类型,进一步增强了代码的复用性。

内存管理

C语言:需要程序员手动进行内存管理,包括使用mallocfree等函数来分配和释放内存。虽然这种方式可以提供较高的控制灵活性,但也容易因内存管理不当而导致内存泄漏、悬空指针等问题。

C++:引入了构造函数、析构函数、RAII(资源获取即初始化)等机制,在一定程度上自动管理资源的生命周期,减少了内存泄漏的风险。例如,当创建一个对象时,构造函数会自动分配所需的资源,而析构函数会在对象销毁时释放这些资源。

标准库支持

C语言:拥有丰富的标准库,涵盖了字符串处理、数学运算、文件操作等方面,能够满足基本的嵌入式应用开发需求。不过,相比于C++的标准库,C语言的标准库功能相对较为基础和简单。

C++:标准库更为庞大和复杂,除了包含C语言标准库的功能外,还提供了标准模板库(STL),其中包括向量、列表、映射等各种容器,以及算法、迭代器等组件,大大提高了开发效率。

代码组织与模块化

C语言:通常使用函数和宏来组织代码,对于较大的项目,可能需要通过分层的文件夹结构和多个源文件来管理代码,但代码之间的耦合度相对较高。

C++:通过类和命名空间等机制,能够更好地将相关的代码封装在一起,形成独立的模块。这种模块化的设计使得代码的结构更加清晰,易于理解和维护,尤其适用于大规模的嵌入式软件项目。

适用场景

C语言:适用于对性能要求极高、硬件资源极度受限的场景,如小型微控制器、实时操作系统内核等底层开发。在这些场景下,C语言的简洁性和高效性能够充分发挥优势,直接对硬件进行精确的控制和操作。

C++:更适合开发复杂的应用程序,特别是那些对代码的可维护性、扩展性和复用性要求较高的项目,如大型工业控制系统、智能汽车电子系统等。C++的面向对象特性和丰富的库支持能够提高开发效率,减少开发时间。

综上所述,C语言以其接近硬件、执行效率高的特点,适合对性能要求苛刻、硬件资源紧张的场景;而C++则凭借其丰富的编程范式、强大的标准库和良好的代码组织性,更适用于复杂的嵌入式应用开发,尤其是对代码质量、开发效率和可维护性有较高要求的项目。在实际的嵌入式应用层开发中,选择哪种语言应根据具体的项目需求、硬件平台以及开发团队的技术栈来决定。

Linux内核绝大部分代码是用C语言编写的,但并不排除在某些特定模块或驱动程序中存在少量用C++实现的地方。不过总体而言,使用C++实现的部分在Linux内核中占比极小(可以认为没有)。

C++的一些高级特性,如面向对象编程、模板、异常处理等,在操作系统内核开发中并不总是适用,并且C++编译器的复杂性和运行时开销可能会增加系统内核的复杂性,降低性能和可靠性。此外,保持内核使用C语言也是为了保证与早期代码的兼容性,确保系统的稳定性和安全性。

C++主要用来进行应用层开发。

为什么很少用C++来进行嵌入式开发?

很少用C++来进行嵌入式开发,主要有以下几方面原因:

性能与资源限制

内存占用大:C++支持面向对象编程,包括类、继承、多态等特性,这些特性会消耗更多的内存。而嵌入式系统的资源通常有限,对内存的使用有严格的限制。例如在一些小型微控制器或者资源紧张的嵌入式设备上,过多的内存占用可能导致系统运行不稳定或者无法正常运行。

执行效率相对较低:C++的运行效率通常比C语言低,因为C++需要进行额外的运行时类型信息(RTTI)处理,进行动态内存分配和释放等操作。在嵌入式系统中,这些额外的开销可能无法接受,尤其是对于一些对实时性要求较高的应用场景。

工具链与编译器支持

编译器成熟度与稳定性:虽然C++编译器已经相当成熟,但在嵌入式系统的开发中,C语言编译器的支持通常更好、更稳定、更可靠。许多嵌入式系统开发者更熟悉和信赖C语言编译器,使用起来更加得心应手。

库支持与兼容性:C语言的标准库相对简单,经过长期的发展和优化,在嵌入式系统中的应用非常广泛。而C++的标准库较为庞大和复杂,在嵌入式环境中可能并不完全适用,并且某些C++库在不同的硬件平台上可能存在兼容性问题。

开发难度与可维护性

学习曲线较陡:C++的语法和特性比C语言更加复杂,对于初学者来说,学习C++需要花费更多的时间和精力。而且,由于C++的面向对象特性,代码的理解和调试也相对困难,这对于嵌入式开发人员的技能要求较高。

代码可读性和可维护性:虽然C++有更强的类型检查和更好的封装性,但C语言也具有其独特的优点,如代码简洁、清晰,易于理解和维护。对于嵌入式系统来说,代码的可读性和可维护性同样重要,尤其是在团队开发和长期维护项目中,简单的代码结构更容易被接受。

历史与生态因素

历史传统的影响:嵌入式系统的发展历史中,C语言一直是最主要的编程语言。很多旧的嵌入式系统和硬件都只支持C语言,因此使用C语言可以确保最大的兼容性。开发人员在使用现有的硬件平台和开发工具时,更倾向于选择C语言。

生态系统的完善程度:围绕C语言建立的嵌入式开发生态系统更加完善,包括大量的开源项目、文档、教程和社区支持。相比之下,C++在嵌入式领域的生态系统相对较小,开发人员在使用过程中可能面临资源不足的问题。

综上所述,尽管C++在软件开发中具有诸多优势,但在嵌入式开发领域,由于性能与资源限制、工具链与编译器支持、开发难度与可维护性以及历史与生态因素等方面的限制,使得C语言仍然占据主导地位。当然,随着技术的不断进步和嵌入式系统的不断发展,C++在嵌入式开发中的应用也在逐渐增加,但目前来看,C语言仍然是嵌入式开发的主流选择。

PS:为什么不用java来进行嵌入式开发?

在嵌入式开发中,Java语言虽然具有诸多优点,但也存在一些局限性和挑战,使得它在某些情况下不是最理想的选择。以下是一些主要原因:

资源占用问题

内存占用较大:Java运行时需要依赖Java虚拟机(JVM),而JVM本身会占用一定的内存空间。对于内存资源有限的嵌入式设备来说,这可能会成为一个较大的负担,影响系统的运行效率。

处理器资源消耗:JVM的运行也需要消耗处理器资源,这在处理能力较弱的嵌入式设备上可能会对系统性能产生明显的影响,导致程序运行缓慢或出现卡顿现象。

实时性欠佳

垃圾回收机制影响:Java的垃圾回收机制虽然可以自动管理内存,但回收的时间和频率是不确定的。在实时性要求较高的嵌入式系统中,垃圾回收可能会导致不可预测的延迟,影响系统的响应速度和实时性能。

JIT编译延迟:部分Java实现采用即时编译技术将字节码编译为本地机器码,这个编译过程可能会引入一定的延迟,对于需要快速响应的嵌入式应用场景不太友好。

硬件控制能力弱

直接操作硬件不便:Java语言的设计初衷是“一次编写,到处运行”,其对硬件的控制力度相对较低。而在嵌入式开发中,开发者经常需要对硬件进行直接的操作和控制,例如访问特定的寄存器、设置中断等,使用Java语言来实现这些操作会增加额外的开发工作量。

底层驱动支持不足:许多嵌入式设备的底层驱动程序通常是用C或汇编语言编写的,Java在这方面的支持相对较弱,可能需要通过JNI等方式进行调用,这也增加了开发的复杂性。

平台适配性差

不同设备定制麻烦:由于嵌入式系统的环境和硬件结构各不相同,在不同的嵌入式设备上需要定制不同的JVM版本,这对开发人员来说是一个巨大的挑战,也增加了开发和维护的成本。

跨平台兼容性问题:尽管Java具有良好的跨平台特性,但在一些特殊的嵌入式硬件平台上,可能会出现兼容性问题,需要进行大量的测试和调试工作。

综上所述,Java语言在嵌入式开发中的使用受到多方面因素的限制。在实际应用中,需要根据具体的项目需求、硬件条件以及开发团队的技术栈来选择合适的编程语言。

C++类成员在声明时的static const等修饰符放在哪个位置

在 C++ 中,类成员的修饰符(如staticconstmutable等)的位置取决于成员类型(变量或函数)和修饰符的种类。以下是详细规则:

一、成员变量的修饰符位置

1. static 修饰的静态成员变量

  • 位置:在类内部声明时,static 必须放在类型前。

  • 示例

    class MyClass {
    public:
        static int count;  // 正确
        // int static count;  // 错误:语法错误
    };
    

2. const 修饰的常量成员变量

  • 位置const 放在类型前或类型后均可(推荐前)。

  • 示例

    class MyClass {
    private:
        const int value = 42;  // 正确(推荐)
        int const value = 42;  // 正确(等价)
    };
    

3. static const 组合修饰的静态常量成员

  • 位置static 在前,const 在后,类型可灵活放置。

  • 示例

    class MyClass {
    public:
        static const int MAX_SIZE = 100;  // 正确(推荐)
        static int const MAX_SIZE = 100;  // 正确(等价)
        // const static int MAX_SIZE = 100;  // C++17前错误,C++17后允许
    };
    

二、成员函数的修饰符位置

1. static 修饰的静态成员函数

  • 位置static 必须放在返回类型前。

  • 示例

    class MyClass {
    public:
        static void func();  // 正确
        // void static func();  // 错误:语法错误
    };
    

2. const 修饰的常量成员函数

  • 位置const 必须放在参数列表后。

  • 示例

    class MyClass {
    public:
        void func() const;  // 正确:表示不修改对象
        // const void func();  // 错误:修饰返回值,非成员函数
    };
    

3. static 和 const 组合修饰

  • 静态常量函数static 在前(返回类型前),const 在后(参数列表后)。

  • 示例

    class MyClass {
    public:
        static int getValue() const;  // 错误:static 函数无 this 指针,不能用 const
        static const int& getRef();   // 正确:返回常量引用
    };
    

三、特殊修饰符的位置

1. mutable 修饰的成员变量

  • 位置mutable 必须放在类型前,只能修饰非静态成员变量。

  • 示例

    class MyClass {
    private:
        mutable int cache;  // 正确:即使在 const 函数中也可修改
        static mutable int count;  // 错误:static 变量不能用 mutable
    };
    

2. virtual 和 override 修饰的成员函数

  • 位置virtual 放在返回类型前,override 放在参数列表后。

  • 示例

    class Base {
    public:
        virtual void func() const;
    };
    
    class Derived : public Base {
    public:
        void func() const override;  // 正确
    };
    

四、总结表格

成员类型

修饰符

正确位置示例

错误位置示例

成员变量

static

static int count;

int static count;

成员变量

const

const int value;

-(语法允许但不推荐)

成员变量

static const

static const int MAX;

const static int MAX;

成员函数

static

static void func();

void static func();

成员函数

const

void func() const;

const void func();(修饰返回值)

成员函数

virtual

virtual void func();

void virtual func();

成员函数

override

void func() override;

override void func();

五、注意事项

  1. 静态成员函数不能用 const 修饰
    因为静态函数没有 this 指针,无法保证 “不修改对象”。

    static void func() const;  // 错误
    
  2. const 修饰返回值与修饰函数的区别

    const int getValue();  // 修饰返回值(常量值)
    int getValue() const;  // 修饰函数(不修改对象)
    
  3. C++17 及以后的 inline 静态常量

    class MyClass {
    public:
        inline static const int MAX = 100;  // 正确(无需类外定义)
    };
    

通过遵循这些规则,可以确保代码的正确性和可读性。

在 C++ 中,const 修饰成员函数时的位置非常关键,它直接影响函数的语义和使用方式。以下是详细解析:

(1)修饰成员函数本身(放在参数列表后)

表示该函数不会修改对象的状态(即 this 指针被视为指向常量的指针)。
语法

return_type function_name(parameters) const;

示例

 
class MyClass {
private:
    int value;
public:
    int getValue() const {  // const 成员函数
        return value;       // 允许:不修改成员变量
        // value = 0;      // 错误:在 const 函数中修改成员变量
    }
};

(2)修饰返回值类型(放在返回类型前)

表示函数返回一个常量值或引用,调用者不能修改返回值。
语法

const return_type function_name(parameters);
示例
class MyClass {
private:
    std::string data;
public:
    const std::string& getData() const {  // 返回常量引用
        return data;
    }
};

// 调用者无法修改返回的引用
MyClass obj;
obj.getData() = "new value";  // 错误:尝试修改常量引用

 更多待补充

C++类中声明的静态变量,还需要在cpp文件中再次定义吗?

在 C++ 中,类的静态成员变量需要在类C外进行定义,除非它是 constexpr 类型。这是因为声明和定义在 C++ 中有严格区分:

一、核心规则:静态成员需类外定义

类内声明 ≠ 类外定义
类内的静态成员变量声明仅声明类型和名称,未分配内存。必须在类外定义并初始化,否则会导致链接错误。

定义语法
在类外使用 类型 类名::变量名 = 初始值; 进行定义:

// MyClass.h
class MyClass {
public:
    static int counter;  // 声明
};

// MyClass.cpp
int MyClass::counter = 0;  // 定义并初始化

二、例外情况:constexpr 静态成员

C++17 起,constexpr 静态成员可直接在类内定义,无需类外重复:

 
// MyClass.h
class MyClass {
public:
    static constexpr int MAX_VALUE = 100;  // 直接定义
};

三、const 整型静态成员的特殊处理

C++ 允许 const 整型(如 intcharenum)在类内声明时初始化,无需类外定义(但仍建议定义以避免 ODR 违规):

// MyClass.h
class MyClass {
public:
    static const int SIZE = 10;  // 合法:类内初始化
};

// 若 SIZE 被取地址或需要外部链接,仍需在 .cpp 中定义:
// const int MyClass::SIZE;  // 定义(无需重复赋值)

四、常见错误与链接问题

1. 未定义静态成员

// MyClass.h
class MyClass {
public:
    static int value;  // 仅声明
};

// 错误:未在 .cpp 中定义
int main() {
    MyClass::value = 42;  // 链接错误:undefined reference to `MyClass::value`
}

2. 重复定义

// 错误:多个编译单元重复定义
// File1.cpp
int MyClass::value = 0;

// File2.cpp
int MyClass::value = 0;  // 重复定义,链接错误

在 C++ 中,静态成员变量的定义可以不显式初始化,此时它会被零初始化(Zero-initialized)

附:

在 C++ 中,constexpr 是一个说明符(Specifier),而非具体类型。它用于声明编译时常量表达式,告诉编译器该变量或函数的值可在编译阶段计算,从而允许在需要常量表达式的上下文中使用。

一、constexpr 的两种主要用法

1. constexpr 变量

声明一个编译时常量,必须在编译阶段初始化:

 
constexpr int MAX_SIZE = 100;  // 编译时常量
int array[MAX_SIZE];           // 合法:数组大小需编译时常量

特性

隐式为 const,不可修改。

必须用编译时常量表达式初始化:

constexpr int square(int x) { return x * x; }
constexpr int value = square(5);  // 编译时计算 5*5=25

2. constexpr 函数

函数可在编译时调用,需满足以下条件:

  • 参数和返回值必须是字面量类型(如 intenum、自定义类)。
  • 函数体必须足够简单,通常只有一条 return 语句。
constexpr int add(int a, int b) {
    return a + b;
}

constexpr int result = add(3, 4);  // 编译时计算 3+4=7

二、constexpr 与 const 的区别

特性constexprconst
编译时计算✅ 必须在编译时确定值❌ 运行时或编译时初始化均可
隐式 const✅ 自动为 const❌ 需显式声明 const
使用场景数组大小、模板参数、枚举值等防止变量被修改
示例constexpr int x = 10;const int y = getValue();

C++不同的类里面可以使用相同名称的函数吗?

在 C++ 中,不同类可以使用相同名称的函数,且这些函数相互独立。C++ 通过类作用域区分同名函数,具体规则如下:

一、基础规则:类作用域隔离同名函数

不同类中的同名函数是完全独立的实体,不会产生命名冲突:

class A {
public:
    void func() { /* A::func 的实现 */ }
};

class B {
public:
    void func() { /* B::func 的实现 */ }  // 与 A::func 不冲突
};

// 使用示例
A a; a.func();  // 调用 A::func
B b; b.func();  // 调用 B::func

二、特殊场景:继承与重载

1. 子类与父类的同名函数

子类中的同名函数会隐藏父类的版本(除非使用 using 声明):

class Parent {
public:
    void func() { /* Parent::func */ }
};

class Child : public Parent {
public:
    void func() { /* Child::func */ }  // 隐藏 Parent::func
};

Child c;
c.func();       // 调用 Child::func
c.Parent::func();  // 显式调用 Parent::func

2. 函数重载(Overload)

同一类中,函数名相同但参数列表不同构成重载:

class Calculator {
public:
    int add(int a, int b) { return a + b; }        // 重载版本 1
    double add(double a, double b) { return a + b; }  // 重载版本 2
};

3. 虚函数与多态

父类的虚函数可在子类中被重写(Override):

class Shape {
public:
    virtual void draw() { /* 基类实现 */ }
};

class Circle : public Shape {
public:
    void draw() override { /* 子类实现 */ }  // 重写虚函数
};

子类和父类函数同名

在 C++ 中,子类定义与父类同名的函数是常见操作,但具体行为取决于函数签名和是否为虚函数。以下是详细分类和示例:

一、函数隐藏(Hiding):最常见场景

若父类函数不是虚函数,子类函数与父类函数名称相同,则子类函数会隐藏所有父类的重载函数:

class Parent {
public:
    void func() { std::cout << "Parent::func()" << std::endl; }
    void func(int x) { std::cout << "Parent::func(int)" << std::endl; }
};

class Child : public Parent {
public:
    void func() { std::cout << "Child::func()" << std::endl; }  // 隐藏 Parent::func()
};

// 使用示例
Child c;
c.func();       // 调用 Child::func()
// c.func(10);  // 错误!父类的 func(int) 被隐藏
c.Parent::func(10);  // 显式调用父类版本

二、函数重写(Override):实现多态

若父类函数为虚函数virtual)且子类函数签名完全相同,则子类函数会重写父类函数,实现运行时多态:

class Shape {
public:
    virtual void draw() { std::cout << "Shape::draw()" << std::endl; }
};

class Circle : public Shape {
public:
    void draw() override { std::cout << "Circle::draw()" << std::endl; }  // 重写
};

// 多态调用
Shape* ptr = new Circle();
ptr->draw();  // 输出:Circle::draw()(运行时绑定)

注意

  • C++11 引入的 override 关键字可显式声明重写,帮助编译器检查签名是否匹配。

  • 若子类函数签名与父类虚函数不完全相同(如参数不同),则退化为隐藏,而非重写。

三、使用 using 声明引入父类函数

若想在子类中保留父类的重载版本,可使用 using 声明

class Parent {
public:
    void func(int x) { std::cout << "Parent::func(int)" << std::endl; }
};

class Child : public Parent {
public:
    using Parent::func;  // 引入父类的所有 func 重载
    void func() { std::cout << "Child::func()" << std::endl; }  // 添加新重载
};

// 使用示例
Child c;
c.func();     // 调用 Child::func()
c.func(10);   // 调用 Parent::func(int)

四、静态函数与同名问题

静态函数也可被继承和隐藏,但无法实现多态(因为静态函数不依赖对象)

class Base {
public:
    static void staticFunc() { std::cout << "Base::staticFunc()" << std::endl; }
};

class Derived : public Base {
public:
    static void staticFunc() { std::cout << "Derived::staticFunc()" << std::endl; }
};

// 使用示例
Base::staticFunc();    // 调用 Base 版本
Derived::staticFunc(); // 调用 Derived 版本

五、总结表格

父类函数特性

子类函数特性

机制

调用方式

非虚函数

名称相同,参数不同

隐藏

child.func() 调用子类版本

非虚函数

名称和参数均相同

隐藏

child.func() 调用子类版本

虚函数(virtual

名称和参数均相同

重写

基类指针调用子类版本(多态)

静态函数

名称相同

隐藏

通过类名区分调用

最佳实践

  • 若期望多态行为,父类函数必须声明为 virtual,子类使用 override 显式标记。

  • 若需保留父类重载版本,在子类中使用 using Parent::func;

  • 避免无意识的隐藏,建议通过函数命名区分功能(如 Parent::doWork() vs Child::doWorkChild())。

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

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

相关文章

【C语言入门级教学】冒泡排序和指针数组

文章目录 1.冒泡排序2.⼆级指针3.指针数组4.指针数组模拟⼆维数组 1.冒泡排序 冒泡排序的核⼼思想&#xff1a;两两相邻的元素进⾏⽐较。 //⽅法1 void bubble_sort(int arr[], int sz)//参数接收数组元素个数 { int i 0;for(i0; i-1; i) { int j 0; for(j0; j-1; j) { …

shell脚本中常用的命令

一、设置主机名称 通过文件的方式修改通过命令修改 二、nmcli 查看网卡 ip a s ens160 (网卡名称) ifconfig ens160 nmcli device show ens160 nmcli device status nmcli connection show ens160 2.设置网卡 a)当网卡没有被设置时 b)网卡被设定&#xff0c;需要修改 三…

Nuxt3部署

最近接了一个项目&#xff0c;需要用到 nuxt3 技术来满足甲方所要求的需求&#xff0c;在部署的时候遇到了很多问题&#xff0c;这里我一一给大家讲述部署流程&#xff0c;以及所遇到的坑 打包部署 部署分为俩种方式&#xff1a; 静态(spa)部署 和 ssr部署 静态部署 静态部…

网络攻防技术一:绪论

文章目录 一、网络空间CyberSpace1、定义2、基本四要素 二、网络空间安全1、定义2、保护对象3、安全属性4、作用空间 三、网络攻击1、攻击分类2、攻击过程 四、网络防护1、定义2、安全模型3、安全服务5类4、特定安全机制8种5、普遍性安全机制5种 五、网络安全技术发展简史1、第…

【人工智能】deepseek七篇论文阅读笔记大纲

七篇文章看了整整五天&#xff0c;加上整理笔记和问ds优化&#xff0c;大致的框架是有了。具体的公式细节比较多&#xff0c;截图也比较麻烦&#xff0c;就不列入大纲去做笔记了。 DeepSeek-LLM&#xff1a;一切的起点&#xff0c;所以探索的东西比较多&#xff0c;包括&#x…

【算法】分支限界

一、基本思想 &#xff08;分支限界&#xff0c; 分枝限界&#xff0c; 分支界限 文献不同说法但都是一样的&#xff09; 分支限界法类似于回溯法&#xff0c;也是一种在问题的解空间树上搜索问题解的算法。 但一般情况下&#xff0c;分支限界法与回溯法的求解目标不同。回溯…

数据库管理与高可用-MySQL全量,增量备份与恢复

目录 #1.1MySQL数据库备份概述 1.1.1数据备份的重要性 1.1.2数据库备份类型 1.1.3常见的备份方法 #2.1数据库完全备份操作 2.1.1物理冷备份与恢复 2.1.2mysqldump备份与恢复 2.1.3MySQL增量备份与恢复 #3.1制定企业备份策略的思路 #4.1扩展&#xff1a;MySQL的GTID 4.1.1My…

从gitee仓库中恢复IDEA项目某一版本

神奇的功能&#xff01;&#xff01;&#xff01;代码改乱了&#xff0c;但是还有救&#xff01; 打开终端&#xff0c;输入git log 复制想要恢复版本的提交哈希值&#xff0c;打开终端输入git reset --hard <哈希值> &#xff0c;就能修复到那时的提交版本了

用dayjs解析时间戳,我被提了bug

引言 前几天开发中突然接到测试提的一个 Bug&#xff0c;说我的时间组件显示异常。 我很诧异&#xff0c;这里初始化数据是后端返回的&#xff0c;我什么也没改&#xff0c;这bug提给我干啥。我去问后端&#xff1a;“这数据是不是有问题&#xff1f;”。后端答&#xff1a;“…

类和对象:实现日期类

目录 概述 一.实现日期类的基本框架 二.实现比较的运算符重载 1.>的运算符重载 2.的运算符重载 3.其余的比较运算符重载 三.加减天数的运算符重载 1.,的运算符重载 2.-&#xff0c;-的运算符重载 3.对1和2的小优化 四.两个日期类相减的重载 1.&#xff0c;--的重…

基于springboot的运动员健康管理系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

华为云Flexus+DeepSeek征文 | 初探华为云ModelArts Studio:部署DeepSeek-V3/R1商用服务的详细步骤

华为云FlexusDeepSeek征文 | 初探华为云ModelArts Studio&#xff1a;部署DeepSeek-V3/R1商用服务的详细步骤 前言一、华为云ModelArts Studio平台介绍1.1 ModelArts Studio介绍1.2 ModelArts Studio主要特点1.3 ModelArts Studio使用场景1.4 ModelArts Studio产品架构 二、访问…

下载即转化的商业密码:解析华为应用商店CPD广告的智能投放逻辑

在移动互联网流量红利见顶的背景下&#xff0c;华为应用市场凭借其终端生态优势正成为开发者获客的新蓝海。数据显示&#xff0c;2025年Q1华为应用商店全球分发量同比增长27%&#xff0c;其中CPD广告因其"下载才付费"的精准特性&#xff0c;已成为金融、游戏、工具类…

分布式锁和数据库锁完成接口幂等性

1、分布式锁 唯一主键与乐观锁的本质是使用了数据库的锁&#xff0c;但由于数据库锁的性能不太好&#xff0c;所以我们可使用Redis、Zookeeper等中间件来实现分布式锁的功能&#xff0c;以Redis为例实现幂等&#xff1a;当用户通过浏览器发起请求&#xff0c;服务端接收到请求…

浅谈JMeter之常见问题Address already in use: connect

浅谈JMeter之常见问题Address already in use: connect 在JMeter高并发测试中出现“address already in use”错误&#xff0c;主要源于Windows系统的TCP端口资源耗尽及连接配置问题&#xff0c;在执行JMeter中查看结果树 原因分析 GET请求默认采用短连接&#xff08;Conne…

【机器学习基础】机器学习入门核心算法:随机森林(Random Forest)

机器学习入门核心算法&#xff1a;随机森林&#xff08;Random Forest&#xff09; 1. 算法逻辑2. 算法原理与数学推导2.1 核心组件2.2 数学推导2.3 OOB&#xff08;Out-of-Bag&#xff09;误差 3. 模型评估评估指标特征重要性可视化 4. 应用案例4.1 医疗诊断4.2 金融风控4.3 遥…

【深度学习】12. VIT与GPT 模型与语言生成:从 GPT-1 到 GPT4

VIT与GPT 模型与语言生成&#xff1a;从 GPT-1 到 GPT4 本教程将介绍 GPT 系列模型的发展历程、结构原理、训练方式以及人类反馈强化学习&#xff08;RLHF&#xff09;对生成对齐的改进。内容涵盖 GPT-1、GPT-2、GPT-3、GPT-3.5&#xff08;InstructGPT&#xff09;、ChatGPT …

常规算法学习

算法 1. 排序算法1. 归并排序1.1 普通归并排序1.2 优化后的归并排序&#xff08;TimSort&#xff09; 2. 插入排序2.1 直接插入排序2.2 二分插入排序2.3 成对插入排序 3. 快速排序3.1 单轴快速排序3.2 双轴快排 4. 计数排序 2. 树1. 红黑树&#xff08;Red Black Tree&#xff…

Google 发布的全新导航库:Jetpack Navigation 3

前言 多年来&#xff0c;Jetpack Navigation 库一直是开发者的重要工具&#xff0c;但随着 Android 用户界面领域的发展&#xff0c;特别是大屏设备的出现和 Jetpack Compose 的兴起&#xff0c;Navigation 的功能也需要与时俱进。 今年的 Google I/O 上重点介绍了 Jetpack Na…

Arbitrum Stylus 合约实战 :Rust 实现 ERC20

在《Arbitrum Stylus 深入解析与 Rust 合约部署实战》篇中&#xff0c;我们深入探讨了 Arbitrum Stylus 的核心技术架构&#xff0c;包括其 MultiVM 机制、Rust 合约开发环境搭建&#xff0c;以及通过 cargo stylus 实现简单计数器合约的部署与测试。Stylus 作为 Arbitrum Nitr…