c++面经总结
C++基础语法
C++和c的区别
-
c++中new和delete是对内存分配的运算符,取代了c中的malloc和free
-
标准c++中的字符串类取代了标准c函数库头文件中的字符数组处理函数(c中没有字符串类型).
-
在c++中,允许有相同的函数名,不过他们的参数类型不能完全相同,这样这些函数就可以相互区分开来。而在c语言中是不允许的。也就是c++可以重载,c不允许.
-
c++用来做控制态输入输出的iostream类库代替了c中的stdio。
-
c++语言中,允许变量定义语句在程序中的任何地方,只要在是使用它之前就可以;而c语言中必须要在函数开头部分的。c++不允许重复定义变量,c语言也是做不到的。
-
c++中,除了值和指针之外,还加了引用。引用变量是其它变量的一个别名,我们可以认为他们只是名字不相同,其它都是相同的
1.c++为什么支持函数重载,c语言不支持函数重载
-
C++代码产生函数符号的时候,函数名+参数列表类型组成的!
-
C代码产生函数符号的时候,函数名来决定! I
重载和重写的区别
-
重载
-
重载是指在同一范围内定义中的同名成员函数才存在重载关系。主要特点就是函数名相同,参数类型和数目有所不同,不能出现参数个数和类型均相等的情况,仅仅依靠返回值不同来区分的函数。重载和函数成员是否是虚函数无关。
-
-
重写
-
重写指的是派生类中覆盖基类中的同名函数,重写就是重写函数体,要求基类函数必须是虚函数且:
-
与基类的虚函数有相同的参数个数
-
与基类的虚函数有相同的参数类型
-
与基类的虚函数有相同的返回值类型
举例子
?//父类 ?class A{ ?public: ? virtual int fun(int a){} ?} ?//子类 ?classB : class A ?{ ?public: ? //重写,一般加override可以确保是重写父类的函数 ? virtual int fun(int a)override{} ?}
-
-
重载与重写的区别:
-
重写是父类和子类之间的垂直关系,重载是不同函数之间的水平
-
重写要求参数列表相同,重载则要求参数列表不同,返回值不要求
-
重写关系中,调用方法根据对象类型决定,重载根据调用时实参表与形参表的对应关系来选择函数体
讲一下c++的三中继承
-
public继承
-
公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,都保持原有的状态,而基类的私有成员任然是私有的,不能被这个派生类的子类所访问。
-
-
protected继承
-
保护继承的特点是基类的所有公有成员和保护成员都称为派生类的保护成员,并且只能被它的派生类成员函数或友元函数所访问,基类的私有成员仍然是私有的,
-
-
private继承
-
私有继承的特点是基类的所有公有成员和保护成员都为派生类的私有成员,并不被他的派生子类所访问,基类的成员只能由派生类访问,无法继续往下访问
-
指针和引用的区别
-
指针是一个变量,存储的是一个地址,引用跟原来的变量实质上是同一个东西,是变量的别名。
-
指针有多级,而引用只有一级
-
指针可以为空,但是引用不能为空且定义时必须初始化
-
指针在初始化后还可以改变指向,但是引用不可以
-
sizeof指针得到的是指针的大小,引用得到是所指向的大小
结构体对齐讲一讲? int double int的大小
抽象类和普通类有什么区别
?//动物的基类 泛指 类-》抽象一个实体的类型 ?//定义Animal 的初衷 并不是让Anmail抽象某个实体的类型 ?//作用: ?//1. string _name 让所有动物实体类通过继承Animal直接复用该属性 ?//2. 给所有的派生类保留同一的覆盖/重写接口 ?//拥有纯虚函数的类 就是抽象类 ?// 抽象类不能实例化对象,但是可以定义指针和引用的变量 ?class Animal{ ?public: ? Animal(string) : _name(name){} ? virtual void bark() = 0;//纯函数 ? ?protected: ? string _name; ?}; ?class Cat : public Animal ?{ ?public: ? Cat(string name) :Animal(name){} ? void bark() { cout<<_name<<"bark :miao miao"<<endl;} ?}; ?class Dog : public Animal ?{ ?public: ? Dog (string name) :Animal(name){} ? void bark() { cout<<_name<<"bark :wang wang"<<endl; ?}; ?//这个如果每个都加下面的话就太麻烦了 如果新新添加或者删除的话就不好了 ?/* ?void bark(Cat &cat) ?{ ? cat.bark(); ?} ?void bark(Dog &dog) ?{ ? dog.bark(); ?} ?*/ ?void bark(Animal *p) ?{ ? p -> bark();//Anmail :: bark 虚函数,动态绑定le ? //p->cat cat vftable &Cat::bark ?} ?//这个就是多态 ?int main() ?{ ? Cat cat("秘密"); ? Dog dog("jj"); ? ? bark(&cat); ? bark(&dog); ?}
一般把什么设置成抽象类
对多态的理解
-
定义:同一事物表现出不同事务的能力,即向不同对象发送同一信息,不同的对象在接受时会产生不同的行为(重载实现编译时多态,虚函数实现运行时多态)
-
功能:多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作;
-
简单一句话:允许将子类类型的指针赋值给父类类型的指针。
实现多态有两种方式 1.覆盖(override):是指子类重新定义父类的虚函数的做法
2.重载(overload):是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)
例如:
基类是一个抽象———人,那学生、运动员也是人,而使用这个抽象对象既可以表示学生、也可以表示运动员。
?class Animal{ ?public: ? Animal(string) : _name(name){} ? virtual void bark() {}//这个是叫的虚函数 ? ?protected: ? string _name; ?}; ?class Cat : public Animal ?{ ?public: ? Cat(string name) :Animal(name){} ? void bark() { cout<<_name<<"bark :miao miao"<<endl;} ?}; ?class Dog : public Animal ?{ ?public: ? Dog (string name) :Animal(name){} ? void bark() { cout<<_name<<"bark :wang wang"<<endl; ?}; ?//这个如果每个都加下面的话就太麻烦了 如果新新添加或者删除的话就不好了 ?/* ?void bark(Cat &cat) ?{ ? cat.bark(); ?} ?void bark(Dog &dog) ?{ ? dog.bark(); ?} ?*/ ?void bark(Animal *p) ?{ ? p -> bark();//Anmail :: bark 虚函数,动态绑定le ? //p->cat cat vftable &Cat::bark ?} ?//这个就是多态 ?int main() ?{ ? Cat cat("秘密"); ? Dog dog("jj"); ? ? bark(&cat); ? bark(&dog); ?}
?std::function 可以容纳不同签名的可调用对象,实现多态性。 ?#include <iostream> ?#include <functional> ?? ?void printMessage(const std::string& message) { ? ? std::cout << message << std::endl; ?} ?? ?void printNumber(int number) { ? ? std::cout << "Number: " << number << std::endl; ?} ?? ?int main() { ? ? // 使用 std::function 容纳不同签名的函数 ? ? std::function<void(const std::string&)> func1 = printMessage; ? ? std::function<void(int)> func2 = printNumber; ?? ? ? // 调用包装的函数 ? ? func1("Hello, world!"); // 输出:Hello, world! ? ? func2(42); ? ? ? ? ? ? ? // 输出:Number: 42 ?? ? ? return 0; ?}
继承在讲一讲
定义:
让某种类型对象获得另一个类型对象的属性和方法。
功能:
它可以使用现有类的所有所能,并在无需重新编写原来的类的情况下对这些功能进行扩展
常见的继承有三种方式:
1、实现继承:指使用基类的属性和方法而无需额外编码的能力。
2、接口继承:指仅使用属性和方法的名额、但是子类必须提供实现的能力
3、可视继承:指子窗体(类)使用基窗体(类)的外观和实现代码的能力
例如:
将人定义为一个抽象类,拥有姓名、性别、年龄等公共属性,吃饭、睡觉等公共方法,在定义一个具体的人时,就可以继承这个抽象类,既保留了公共属性和方法,也可以在此基础上扩展跳舞、唱歌等特有方法。
?class A{ ?public: ? int ma; ?protive: ? int mb; ?private: ? int mc; ?} ?class B : public A{ ?public: ? int md; ?protive: ? int me; ?private: ? int mf; ?? ?}
-
派生类从继承可以继承来所有的成员(变量和方法),除过构造函数和析构函数I
-
派生类怎么初始化从基类继承来的成员变量呢?解答: 通过调用基类相应的构造函数来初始化
-
派生类的构造函数和析构函数,负责初始化和清理派生类部分派生类从基类继承来的成员,的初始化和清理由谁负责呢?
是由基类的构造和析构函数来负责
派生类对象构造和析构的过程是: 1.派生类调用基类的构造函数,初始化从基类继承来的成员.
2.调用派生类自己的构造函数,初始化派生类自己特有的成员.派生类对象的作用域到期了 3.调用派生类的析构函数,释放派生类成员可能占用的外部资源(堆内存,文件)
4.调用基类的析构函数,释放派生类内存中于从基类继承来的成员可能占用的外部资源(堆内存,文件)
虚函数调用过程
-
调用过程:
当调用虚函数时,编译器会生成一个虚函数表,其中包含了类的虚函数地址。每个对象都包含一个指向虚函数表的指针(通常是在对象的内存布局中的一个隐藏字段)。当您通过基类指针或引用调用虚函数时,实际上是根据该对象的虚函数表来调用适当的函数。这确保了正确的函数版本被调用。
总的来说,虚函数的调用过程包括定义虚函数,创建派生类,创建对象,然后通过基类指针或引用来调用虚函数。在运行时,根据对象的实际类型确定调用的函数版本,这样可以实现多态性。这使得c++能够在运行时动态选择正确的函数,而不是在编译时静态确定。
定义一个Base 类型的指针 如果 Base中show不是虚函数 就直接调用 静态绑定 pb Base* 如果是虚函数 *pb 指向的就是Derive派生类对象变成动态绑定
虚函数讲一讲
虚函数是指一个类中你希望重载的成员函数。当你用一个基类指针或引用指向一个继承类对象的时候,调用一个虚函数时,实际调用的是继承类的版本。虚函数的主要目的实现多态,这是面向对象编程的一个重要特性。
例如:
?#include <iostream> ?class Animal { ?public: ? ? virtual void makeSound() { ? ? ? ? std::cout << "The animal makes a sound" << std::endl; ? ? } ?}; ?? ?class Dog : public Animal { ?public: ? ? void makeSound() override { ? ? ? ? std::cout << "The dog barks" << std::endl; ? ? } ?}; ?? ?int main() { ? ? Animal* animal = new Dog(); ? ? animal->makeSound(); // 输出 "The dog barks" ? ? delete animal; ? ? return 0; ?} ??
在这个例子中,Animal
类有一个虚函数 makeSound()
,Dog
类继承了 Animal
类并重写了 makeSound()
函数。当我们通过基类指针调用 makeSound()
时,实际上调用的是派生类 Dog
的实现。这就是虚函数实现多态的一个例子
new和delete的原理
实现过程:
-
new的实现过程是:调用名为operator new的标准库函数,分配足够大的原始类为类型化的内存,以保寸指定类型的一个对象;接下来运行该类型的一个构造函数,用指定初始化构造函数;最后返回指向新分配并构造后的对象指针
-
?new 用于在堆(heap)上分配内存空间以存储一个或多个对象。 ?new 会调用适当的构造函数来初始化对象。 ?new 返回指向分配内存的指针,允许您在程序的其他地方访问该对象。 ?如果分配失败(例如,没有足够的内存可用),new 可能引发 std::bad_alloc 异常,您可以使用 try-catch 块来捕获它。
-
delete的实现过程:对指针指向的对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用的内存。
-
?delete 用于释放先前由 new 分配的内存。 ?delete 会调用适当的析构函数来销毁对象。 ?delete 接受指向要释放内存的指针,并将该内存返回给系统堆池,以便将其重复利用。 ?如果使用 delete 后未将指针设置为 nullptr(或 NULL),则可能会导致悬空指针(dangling pointer)问题。
new和malloc
1、 new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持;
2、 使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
3、 new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
4、 new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
5、 new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
C++中有几种类型的new
在C++中,new有三种典型的使用方法:plain new,nothrow new和placement new
vector中push_back和emplace_back的区别
内联函数?内联函数的缺点?
仿函数
关键字
const 关键字
const的作?:
被它修饰的值不能改变,是只读变量。必须在定义的时候就给它赋初值。
1、常量指针(底层const )
常量指针:
是指定义了?个指针,这个指针指向?个只读的对象,不能通过常量指针来改变这个对象的值。常量指针强调的是
指针对其所指对象的不可改变性。
特点:
靠近变量名。
形式:
(1)const 数据类型 *指针变量 = 变量名
(2)数据类型 const *指针变量 = 变量名
?例:
?int temp = 10; ?? ?const int* a = &temp; ?? ?int const *a = &temp; ?? ?// 更改: ?? ?*a = 9; // 错误:只读对象 ?? ?temp = 9; // 正确
2、指针常量(顶层const)
指针常量:
指针常量是指定义了?个指针,这个指针的值只能在定义时初始化,其他地?不能改变。指针常量强调的是指针的
不可改变性。
特点:
靠近变量类型。
形式:
数据类型 * const 指针变量=变量名
?int temp = 10; ?int temp1 = 12; ?int* const p = &temp; ?// 更改: ?p = &temp2; // 错误 ?*p = 9; // 正确
define 和 typedef 的区别
define
-
只是简单的字符串替换,没有类型检查
-
是在编译的预处理阶段起作?
-
可以?来防?头?件重复引?
-
不分配内存,给出的是?即数,有多少次使?就进?多少次替换
typedef
-
有对应的数据类型,是要进?判断的
-
是在编译、运?的时候起作?
-
在静态存储区中分配空间,在程序运?过程中内存中只有?个拷贝
define 和 inline 的区别
1、define:
定义预编译时处理的宏,只是简单的字符串替换,?类型检查,不安全。
2、inline:
int temp = 10;
int temp1 = 12;
int* const p = &temp;
// 更改:
p = &temp2; // 错误
*p = 9; // 正确inline是先将内联函数编译完成?成了函数体直接插?被调?的地?,减少了压栈,跳转和返回的操作。没有普
通
函数调?时的额外开销;
内联函数是?种特殊的函数,会进?类型检查;
对编译器的?种请求,编译器有可能拒绝这种请求;
C++中inline编译限制:
-
不能存在任何形式的循环语句
-
不能存在过多的条件判断语句
-
函数体不能过于庞?
-
内联函数声明必须在调?语句之前
override 和 overload
1、override是重写(覆盖)了?个?法
以实现不同的功能,?般是?于?类在继承?类时,重写?类?法。
规则:
-
重写?法的参数列表,返回值,所抛出的异常与被重写?法?致
-
被重写的?法不能为private
-
静态?法不能被重写为?静态的?法
-
重写?法的访问修饰符?定要?于被重写?法的访问修饰符(public>protected>default>private)
2、overload是重载,这些?法的名称相同?参数形式不同
?个?法有不同的版本,存在于?个类中。
规则:
-
不能通过访问权限、返回类型、抛出的异常进?重载
-
不同的参数类型可以是不同的参数类型,不同的参数个数,不同的参数顺序(参数类型必须不?样)
-
?法的异常类型和数?不会对重载造成影响
使?多态是为了避免在?类??量重载引起代码臃肿且难于维护。
重写与重载的本质区别是,加?了override的修饰符的?法,此?法始终只有?个被你使?的?法。
new 和 malloc
1、new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。
2、使?new操作符申请内存分配时?须指定内存块的??,?malloc则需要显式地指出所需内存的尺?。
3、opeartor new /operator delete可以被重载,?malloc/free并不允许重载。
4、new/delete会调?对象的构造函数/析构函数以完成对象的构造/析构。?malloc则不会
5、malloc与free是C++/C语?的标准库函数,new/delete是C++的运算符
6、new操作符从?由存储区上为对象动态分配内存空间,?malloc函数从堆上动态分配内存。
constexpr 和 const
const 表?“只读”的语义,constexpr 表?“常量”的语义
constexpr 只能定义编译期常量,? const 可以定义编译期常量,也可以定义运?期常量。
你将?个成员函数标记为constexpr,则顺带也将它标记为了const。如果你将?个变量标记为constexpr,则同样它
是const的。但相反并不成?,?个const的变量或函数,并不是constexpr的。
static
作?:实现多个对象之间的数据共享 + 隐藏,并且使?静态成员还不会破坏隐藏原则;默认初始化为0
前置++与后置++
?self &operator++() { ? node = (linktype)((node).next); ? return *this; ?} ?const self operator++(int) { ? self tmp = *this; ? ++*this; ? return tmp; ?}
为了区分前后置,重载函数是以参数类型来区分,在调?的时候,编译器默默给int指定为?个0
1、为什么后置返回对象,?不是引?
因为后置为了返回旧值创建了?个临时对象,在函数结束的时候这个对象就会被销毁,如果返回引?,那么我请问
你?你的对象对象都被销毁了,你引?啥呢?
2、为什么后置前?也要加const
其实也可以不加,但是为了防?你使?i++++,连续两次的调?后置++重载符,为什么呢?
原因:
它与内置类型?为不?致;你?法活得你所期望的结果,因为第?次返回的是旧值,?不是原对象,你调?两次后
置++,结果只累加了?次,所以我们必须?动禁?其合法化,就要在前?加上const。
3、处理?户的?定义类型
最好使?前置++,因为他不会创建临时对象,进?不会带来构造和析构?造成的格外开销。
c和c++中const的区别是什么
-
const的编译方式不同,c中,const就是当作一个变量来编译生成指令的.
-
c++中,所有出现const常量名字的地方,都被常量的初始化替换了! !!
bind的用法
我们通常是在 C++ 中进行函数绑定的上下文中。std::bind
是一个非常有用的函数模板,它允许你创建一个可调用对象(函数、函数指针、成员函数等),并绑定一些参数或者改变调用时的顺序。这对于在一些场景中很方便,特别是在涉及回调、函数对象、线程等情况下。
以下是一个简单的例子来说明 std::bind
绑定全局函数的基本用法:
?#includ<iostream> ?#include<function> ?//一个简单的函数 ?void greet(consr std::string& name,const std::string& greeting){ ? std::cont<<greeting<<", "<<name<<"!"<<std::endl; ?} ?int main(){ ? //使用bind绑定器绑定greet函数的第一个参数 ? auto greetSomeone = std::bind(greet, std::placeholders::_1,"hello"); ? //调用可以调用的对象 ? greetSomeone("Alice");//输出 hello,Alice! ? greetSomeone("Bob");//输出hello,Bob! ?} ??
在这个例子中,std::bind
用于创建一个可调用对象 greetSomeone
,它是 greet
函数的一个变体,其中第一个参数(name
)被绑定为 std::placeholders::_1
,而第二个参数(greeting
)被指定为常量字符串 "Hello"。
bind绑定非静态成员函数
?#include <functional> ?#include <iostream> ?? ?class MyClass { ?public: ? ? void printMessage(const std::string& message) { ? ? ? ? std::cout << "Message: " << message << std::endl; ? ? } ?}; ?? ?int main() { ? ? MyClass obj; ?? ? ? // 使用 std::bind 绑定非静态成员函数 ? ? auto func = std::bind(&MyClass::printMessage, &obj, std::placeholders::_1); ?? ? ? // 调用包装的成员函数 ? ? func("Hello, world!"); // 输出:Message: Hello, world! ?? ? ? return 0; ?} ??
在这个例子中,使用 std::bind
绑定了 MyClass
中的非静态成员函数 printMessage
。需要注意的是,&obj
作为额外的参数传递给 std::bind
,代表了 this
指针。
绑定成员函数和对象
如果你希望在绑定时不显式传递对象,而是在调用时传递,可以使用 std::placeholders::_1
占位符占位。
?#include <functional> ?#include <iostream> ?? ?class MyClass { ?public: ? ? void printMessage(const std::string& message) { ? ? ? ? std::cout << "Message: " << message << std::endl; ? ? } ?}; ?? ?int main() { ? ? MyClass obj; ?? ? ? // 使用 std::bind 绑定成员函数和对象 ? ? auto func = std::bind(&MyClass::printMessage, std::placeholders::_1); ?? ? ? // 调用包装的成员函数,传递对象作为参数 ? ? func(obj, "Hello, world!"); // 输出:Message: Hello, world! ?? ? ? return 0; ?}
在这个例子中,std::placeholders::_1
占位符占位了对象参数,而在调用时通过 func(obj, "Hello, world!")
传递了实际的对象。
一些关键概念:
-
std::placeholders::_1
:这是一个占位符,表示绑定时应该被替代的第一个参数。在这里,它代表greetSomeone
被调用时传递的参数。 -
greetSomeone
是一个可调用对象,它被绑定到greet
函数,并预设了一个参数("Hello"),所以当你调用greetSomeone("Alice")
时,实际上就是调用了greet("Alice", "Hello")
。
function的用法
std::function
提供了一种通用的、类型安全的方式来包装和存储可调用对象。以下是 std::function
的一些详细用法:
基本用法
?#include <iostream> ?#include <functional> ?? ?int add(int a, int b) { ? ? return a + b; ?} ?? ?int main() { ? ? // 使用 std::function 包装函数 ? ? std::function<int(int, int)> func = add; ?? ? ? // 调用包装的函数 ? ? int result = func(3, 4); ? ? std::cout << "Result: " << result << std::endl; // 输出:Result: 7 ?? ? ? return 0; ?} ??
Lambda 表达式
?#include <iostream> ?#include <functional> ?? ?int main() { ? ? // 使用 std::function 包装 Lambda 表达式 ? ? std::function<int(int, int)> func = [](int a, int b) { ? ? ? ? return a * b; ? ? }; ?? ? ? // 调用包装的 Lambda 表达式 ? ? int result = func(3, 4); ? ? std::cout << "Result: " << result << std::endl; // 输出:Result: 12 ?? ? ? return 0; ?} ??
函数对象
?#include <iostream> ?#include <functional> ?? ?// 函数对象 ?struct Multiply { ? ? int operator()(int a, int b) const { ? ? ? ? return a * b; ? ? } ?}; ?? ?int main() { ? ? // 使用 std::function 包装函数对象 ? ? std::function<int(int, int)> func = Multiply(); ?? ? ? // 调用包装的函数对象 ? ? int result = func(3, 4); ? ? std::cout << "Result: " << result << std::endl; // 输出:Result: 12 ?? ? ? return 0; ?} ??
多态性
?#include <iostream> ?#include <functional> ?? ?void printMessage(const std::string& message) { ? ? std::cout << message << std::endl; ?} ?? ?void printNumber(int number) { ? ? std::cout << "Number: " << number << std::endl; ?} ?? ?int main() { ? ? // 使用 std::function 容纳不同签名的函数 ? ? std::function<void(const std::string&)> func1 = printMessage; ? ? std::function<void(int)> func2 = printNumber; ?? ? ? // 调用包装的函数 ? ? func1("Hello, world!"); // 输出:Hello, world! ? ? func2(42); ? ? ? ? ? ? ? // 输出:Number: 42 ?? ? ? return 0; ?} ??
绑定参数
?#include <iostream> ?#include <functional> ?? ?int add(int a, int b) { ? ? return a + b; ?} ?? ?int main() { ? ? // 使用 std::bind 绑定参数 ? ? std::function<int(int)> add5 = std::bind(add, std::placeholders::_1, 5); ?? ? ? // 调用包装的函数 ? ? int result = add5(10); ? ? std::cout << "Result: " << result << std::endl; // 输出:Result: 15 ?? ? ? return 0; ?} ??
这些示例涵盖了 std::function
的基本用法,包括包装函数、Lambda 表达式、函数对象、多态性、绑定参数以及清空或重置等方面。希望这能够帮助你更好地理解和使用 std::function
。
Protocol Buffers(protobuf)是一种由Google开发的轻量级的数据交换格式,它可以用于结构化数据的序列化,类似于 XML 或 JSON,但更轻量、更高效。Protocol Buffers 不仅用于数据的序列化和反序列化,还可以定义数据结构和接口。
以下是 Protocol Buffers 序列化的基本原理和用法:
Protocol Buffers 的定义
Protocol Buffers 使用 .proto
文件来定义数据结构和消息格式。一个简单的 .proto
文件如下:
?protoCopy codesyntax = "proto3"; ?? ?message Person { ? required string name = 1; ? required int32 id = 2; ? optional string email = 3; ?}
在这个例子中,我们定义了一个名为 Person
的消息,其中包含了 name
、id
和 email
三个字段。每个字段都有一个唯一的标识号,用于在二进制序列中标识字段。
使用 Protocol Buffers 编译器
将 .proto
文件编译成对应的编程语言的类文件,可以使用 Protocol Buffers 编译器。例如,使用 protoc
编译器可以将上述 .proto
文件编译成 C++ 代码:
?bashCopy code ?protoc --cpp_out=. your_proto_file.proto
序列化和反序列化
在编程语言中,你可以使用生成的 Protocol Buffers 类来序列化和反序列化数据。以下是一个简单的 C++ 示例:
?cppCopy code#include "your_proto_file.pb.h" ?#include <iostream> ?#include <fstream> ?? ?int main() { ? ? // 创建一个 Person 对象 ? ? Person person; ? ? person.set_name("John Doe"); ? ? person.set_id(123); ? ? person.set_email("john.doe@example.com"); ?? ? ? // 序列化 ? ? std::string serializedData; ? ? person.SerializeToString(&serializedData); ?? ? ? // 反序列化 ? ? Person deserializedPerson; ? ? deserializedPerson.ParseFromString(serializedData); ?? ? ? // 输出反序列化后的数据 ? ? std::cout << "Name: " << deserializedPerson.name() << std::endl; ? ? std::cout << "ID: " << deserializedPerson.id() << std::endl; ? ? std::cout << "Email: " << deserializedPerson.email() << std::endl; ?? ? ? return 0; ?}
在这个例子中,我们使用 SerializeToString
将 Person
对象序列化为字符串,然后使用 ParseFromString
将字符串反序列化为另一个 Person
对象。序列化后的数据可以存储在文件中、通过网络传输等方式。
4. Protocol Buffers 的优势
-
高效性: Protocol Buffers 使用二进制格式,相比 XML 和 JSON 更加紧凑,占用更少的空间。
-
速度快: 序列化和反序列化的速度比 XML 和 JSON 更快。
-
可扩展: 可以在不破坏现有数据的情况下轻松添加或删除字段,使其具有很好的向前和向后兼容性。
总体而言,Protocol Buffers 是一种强大且高效的数据序列化工具,特别适用于需要高性能和小数据大小的场景,如分布式系统通信、数据存储和通信协议定义。
序列化
序列化是将数据结构或对象转换为字节流(二进制数据)或其他格式的过程,以便将其存储在文件中、通过网络发送,或在不同系统之间传输。序列化后的数据通常可以被反序列化还原为原始的数据结构或对象。
为什么需要序列化
-
数据持久化: 将数据保存在文件中,以便在程序关闭后重新加载。
-
数据传输: 在网络上传输数据,例如客户端和服务器之间的通信。
-
进程间通信: 在不同的进程之间共享数据。
json序列化
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,广泛用于前后端之间的数据传输。JSON 序列化即将数据结构或对象转换为 JSON 格式的字符串,而 JSON 反序列化则是将 JSON 格式的字符串还原为原始的数据结构或对象。
Lambda表达式
Lambda 表达式是 C++11 引入的一项特性,它允许在代码中以一种更为简洁和方便的方式定义匿名函数。Lambda 表达式的本质可以从以下几个方面来理解:
-
匿名函数: Lambda 表达式实际上是一个匿名函数,即没有显式名称的函数。它允许你在需要函数的地方直接定义函数,而不必显式地写出函数名。
-
语法糖: Lambda 表达式是 C++ 对函数对象(function object)的一种语法糖,使得使用函数对象更为简洁。在使用 Lambda 表达式之前,我们通常需要使用函数对象或函数指针来传递函数,Lambda 表达式使这一过程变得更加直观。
-
闭包: Lambda 表达式可以捕获其定义位置的变量,形成一个闭包(closure)。通过捕获变量,Lambda 表达式可以在其生命周期内访问这些变量,即使这些变量在定义 Lambda 表达式的作用域之外。
-
?#icnlude<iostream> ?int main() ?{ ? //lambda表达式没有捕获变量 ? auto add = [](int a,int b){ ? return a + b; ? }; ? //使用lamabda表达式 ? int result = add(2,3); ? cuot<<"result:"<<result<<endl; ? return 0; ?}
在这个例子中,
auto add = [](int a, int b) { return a + b; };
定义了一个 Lambda 表达式,它接受两个整数参数并返回它们的和。Lambda 表达式使用auto
关键字,因为编译器能够推断出 Lambda 表达式的类型。Lambda 表达式的本质是一种语法糖,它提供了更简洁的语法来定义函数,并允许在函数内部捕获外部变量,使得代码更为灵活和直观。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!