面向对象基础内容参考:
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) 需手动 delete MyClass* 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->myFunction()
来调用;如果是引用类型的变量refObj,则可以使用refObj.myFunction()
来调用。✔
静态成员函数
无需对象即可调用:静态成员函数是属于整个类的函数,而不是属于某个特定对象的函数。因此,可以直接通过类名来调用静态成员函数,而不需要创建类的对象。例如,对于上述的MyClass类,如果其中定义了一个静态成员函数staticFunction,那么可以直接使用
MyClass::staticFunction()
来调用该函数。通过对象或指针间接调用:虽然静态成员函数可以直接通过类名调用,但也可以通过对象或指向对象的指针来调用。不过,这种调用方式相对较少见,因为静态成员函数本身与对象无关,直接通过类名调用更加直观和符合逻辑。
✔
普通全局函数
无需对象即可调用:普通全局函数是指在类之外定义的函数,它们不属于任何类,因此不需要使用对象来调用。只要在程序的作用域内声明了该函数,就可以直接通过函数名来调用它。例如,定义了一个全局函数globalFunction,那么可以在任何地方直接使用
globalFunction()
来调用该函数。综上所述,C++中的函数调用是否需要使用对象取决于函数的类型和定义方式。对于非静态成员函数,通常需要通过对象来调用;对于静态成员函数和普通全局函数,则可以直接通过类名或函数名来调用。
嵌入式的应用层用C++开发和用C开发有什么区别?
嵌入式的应用层开发中,使用C语言和C++各有特点,具体区别如下:
✔
开发范式
C语言:采用面向过程的编程范式,以函数为基本单位组织代码,程序的执行流程按照预先设定的函数调用顺序进行。这种方式在处理简单的逻辑和流程时非常直接和高效,但对于复杂的系统,代码的可读性和可维护性可能会随着系统规模的增大而降低。
C++:支持面向对象、泛型、过程化等多种编程范式。通过类和对象的概念,将数据和操作封装在一起,提高了代码的模块化和可维护性。同时,C++的模板机制允许编写通用的代码,适用于各种不同的数据类型,进一步增强了代码的复用性。
✔
内存管理
C语言:需要程序员手动进行内存管理,包括使用
malloc
和free
等函数来分配和释放内存。虽然这种方式可以提供较高的控制灵活性,但也容易因内存管理不当而导致内存泄漏、悬空指针等问题。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++ 中,类成员的修饰符(如
static
、const
、mutable
等)的位置取决于成员类型(变量或函数)和修饰符的种类。以下是详细规则:一、成员变量的修饰符位置
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();
五、注意事项
静态成员函数不能用
const
修饰:
因为静态函数没有this
指针,无法保证 “不修改对象”。static void func() const; // 错误
const
修饰返回值与修饰函数的区别:const int getValue(); // 修饰返回值(常量值) int getValue() const; // 修饰函数(不修改对象)
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
整型(如int
、char
、enum
)在类内声明时初始化,无需类外定义(但仍建议定义以避免 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
函数函数可在编译时调用,需满足以下条件:
- 参数和返回值必须是字面量类型(如
int
、enum
、自定义类)。- 函数体必须足够简单,通常只有一条
return
语句。constexpr int add(int a, int b) { return a + b; } constexpr int result = add(3, 4); // 编译时计算 3+4=7
二、
constexpr
与const
的区别
特性 constexpr
const
编译时计算 ✅ 必须在编译时确定值 ❌ 运行时或编译时初始化均可 隐式 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()
vsChild::doWorkChild()
)。