C++面向对象(二)

article/2025/7/13 8:21:01

面向对象基础内容参考:

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;// 每次创建对象时增加 1objectCount++;}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);    // 声明 box1Box 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;// 每次创建对象时增加 1objectCount++;}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);    // 声明 box1Box 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: 8return 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 指针,不能用 conststatic 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; }        // 重载版本 1double 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.hkcw.cn/article/lSDlIHIYoN.shtml

相关文章

基于AIS的海洋观测应用

知识星球&#xff1a;数据书局。打算通过知识星球将这些年积累的知识、经验分享出来&#xff0c;让各位在数据治理、数据分析的路上少走弯路&#xff0c;另外星球也方便动态更新最近的资料&#xff0c;提供各位一起讨论数据的小圈子 1.背景 船舶自动识别系统&#xff08;Aut…

imx6ull(0):烧录、启动

参考内容&#xff1a; i.MX6ULL Applications Processors for Industrial Products i.MX6ULLApplicationsProcessorReferenceManual 正点原子 I.MX6U嵌入式Linux驱动开发指南 以及 广大工程师们在互联网上分享的学习笔记(一样东西学的人多的时候所带来的优势) 例如这里我用…

CloudCompare——使用CSF算法进行点云高程归一化

目录 1.算法原理2.软件操作2.1 CSF算法2.2 生成CSF网格2.3 平滑网格(可选)2.4 计算点云到网格的距离2.5 将计算得到的距离赋值给高程 3.结果展示3.1 原始点云3.2 归一化结果 1.算法原理 点云高程归一化的关键在于获取原始点云地面数据的DEM。可选取CSF算法提取样地点云地面DEM。…

【C语言】C语言经典小游戏:贪吃蛇(下)

文章目录 一、游戏前准备二、游戏开始1、游戏开始函数&#xff08;GameStart&#xff09;1&#xff09;打印欢迎界⾯&#xff08;WelcomeToGame&#xff09;2&#xff09;创建地图&#xff08;CreateMap&#xff09;3&#xff09;初始化蛇⾝&#xff08;InitSnake&#xff09;4…

循序渐进 Android Binder(一):IPC 基本概念和 AIDL 跨进程通信的简单实例

Binder 给人的第一印象是”捆绑者“&#xff0c;即将两个需要建立关系的事物用某些工具束缚在一起。在 Android 中&#xff0c;Binder 是一种高效的跨进程通信&#xff08;IPC&#xff09;机制&#xff0c;它将可以将运行在不同进程中的组件进行绑定&#xff0c;以实现彼此通信…

ISBN书号查询接口如何用PHP实现调用?

一、什么是ISBN书号查询接口 ISBN数据查询接口是一项图书信息查询服务。它基于全球通用的ISBN编码系统&#xff0c;帮助用户快速获取图书的详细信息&#xff0c;包括书名、作者、出版社、出版时间、价格、封面等关键字段。 该接口广泛应用于电商平台、图书馆管理系统、二手书…

Linux(信号)

目录 一 什么是信号 二 Linux中的信号 1. 查看信号&#xff1a;kill -l 2. 自定义信号的处理方式 2.1 API 2.2 demo 3. 理解信号的发送 4. 信号产生的方式 三 信号保存 四 捕捉信号 1. 先来说说硬件中断&#xff1a; 1. 谁调度操作系统&#xff1f; 2. 理解时间片…

[Windows] Simple Live v1.8.3 开源聚合直播 :支持哔哩哔哩 虎牙 斗鱼 抖音

Simple Live 是一款基于 AllLive 项目 开发的开源聚合直播 APP&#xff0c;支持 哔哩哔哩、虎牙、斗鱼、抖音 等主流平台&#xff0c;具备 无广告、低占用、弹幕互动 等核心优势。其核心功能包括&#xff1a;全平台覆盖&#xff1a;一站式聚合多平台直播资源&#xff0c;无需切…

第十天:Java反射

反射 反射就是&#xff1a;加载类&#xff0c;并编写代码获取类中的成员变量&#xff0c;方法&#xff0c;构造器等。 注意&#xff1a;反射&#xff0c;注解&#xff0c;动态代理就是用来学习框架做框架的&#xff0c;在平时业务开发需求上很少用到。 1 反射学什么&#xf…

整数有约 | 刘乾专访:继续预训练策略与数据优化之道

人工智能多语言处理近年来得到了极大的关注&#xff0c;尤其是在以东南亚为代表的小语种环境中&#xff0c;其特殊的语言多样性和语料库稀缺性使得研究挑战和机遇并存。在现有的自然语言处理模型中&#xff0c;英语和中文因为有海量高质量数据的支持&#xff0c;常被作为核心语…

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

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

抖音商城抓包 分析

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 抓包展示 总结 1.出于安全考虑,本章未…

uniapp-商城-77-shop(8.2-商品列表,地址信息添加,级联选择器picker)

地址信息,在我们支付订单上有这样一个接口,就是物流方式,一个自提,我们就显示商家地址。一个是外送,就是用户自己填写的地址。 这里先说说用户的地址添加。需要使用到的一些方式方法,主要有关于地址选择器,就是uni-data-picker级联选择。 该文介绍了电商应用中地址信息处…

AlmaLinux OS 10 正式发布:兼容 RHEL 10 带来多项技术革新

AlmaLinux OS 基金会日前宣布推出 AlmaLinux OS 10&#xff0c;该版本代号代号紫色的狮子 (Purple Lion)&#xff0c;新版本带来多项新功能和技术更新&#xff0c;旨在为用户提供更强大的企业级 Linux 体验。 该系统使用与 RHEL 10 相同的源代码构建并于 RHEL 10 保持完全兼…

深入理解C# MVVM模式:从理论到实践

在现代软件开发中&#xff0c;良好的架构设计对于构建可维护、可测试和可扩展的应用程序至关重要。Model-View-ViewModel (MVVM) 是一种特别适合XAML-based应用程序&#xff08;如WPF、Xamarin和UWP&#xff09;的架构模式。本文将全面探讨MVVM模式的概念、实现细节、最佳实践以…

Git GitHub Gitee

一、Git 是一个免费、开源的分布式版本控制系统。 版本控制&#xff1a;一种记录文件内容变化&#xff0c;以便将来查阅特定版本修订情况的系统。它最重要的就是可以记录文件修改历史记录&#xff0c;从而让用户可以看历史版本&#xff0c;方便版本切换。 1.和集中式版本控制…

数据库管理-第332期 大数据已死,那什么当立?(20250602)

数据库管理332期 2025-06-02 数据库管理-第332期 大数据已死&#xff0c;那什么当立&#xff1f;&#xff08;20250602&#xff09;1 概念还是技术2 必然的大数据量3 离线到实时4 未来总结 数据库管理-第332期 大数据已死&#xff0c;那什么当立&#xff1f;&#xff08;202506…

Java Netty 中处理粘包和半包问题的解决方案 | TCP消息完整性校验(XOR )

文章目录 引言I 处理TCP粘包和半包问题背景粘包问题的产生原因解决方案WebSocket中的粘包和半包问题及解决方案II Java Netty 中处理粘包和半包问题粘包和半包问题可以通过以下几种方式解决:使用分隔符解码器基于长度字段的解码器实现自定义解码器III TCP常见封装处理消息接收…

鸿蒙next系统以后会取代安卓吗?

点击上方关注 “终端研发部” 设为“星标”&#xff0c;和你一起掌握更多数据库知识 官方可没说过取代谁谁&#xff0c;三足鼎立不好吗&#xff1f;三分天下&#xff0c;并立共存。 鸿蒙基于Linux&#xff0c;有人说套壳&#xff1b;ios/macos基于Unix&#xff0c;说它ios开源了…

排便不是一件可以随意“延后”的事:长期便秘->直肠敏感性降低->功能性便秘->大便失禁

文章目录 引言知识扩展: 快乐排便的黄金姿势I 便秘并不是一种单一成因的疾病便秘成因临床治疗II 总是压抑排便,身体会发生的变化III 排便不是一件可以随意“延后”的事引言 排便是一种复杂的反射行为: 由“直肠充盈—产生便意—括约肌协调—排出”的完整生理链条完成的。 …