大小端存储与判定
- 小端模式的优势,为啥50年前的怪异设计,依然用在现代手机电脑上?
- 大端存储:“人类读的正常顺序”() msb在低位 (最高有效位,英文全称是:the Most Significant Bit,缩写为:msb,是指一个n位二进制数字中的n-1位,具有最高的权值2^(n-1)。与之相反的称之为最低有效位。在大端序中,msb即指最左端的位。)
- 小端存储:略
#include <iostream>
using namespace std;
bool IsBigEndian()
{
int a = 0x1234;
char b = *(char *)&a; //通过将int强制类型转换成char单字节,即等于 取b等于a的低地址部分
if( b == 0x12)
{
cout<< "big end"; return true;
}
cout<< "small end";
return false;
}
int main(){
cout<< IsBigEndian();
return 0;
}
#include <iostream>
using namespace std;
int main(){
int i= 48;
char c = *( (char*)(&i) );
if(c==0) cout<< "small end"; else cout<< "big end";
return 0;
}
C++中不能直接比较浮点类型吗?
在使用浮点数进行比较时,需要注意精度误差的问题。由于浮点数的精度受限于二进制表示法,因此不同的浮点数运算可能会存在精度误差。为此,我们一般不使用“==”运算符直接比较两个浮点数是否相等。可以通过计算它们的差值是否在某个极小的范围内来进行比较,例如:
#include <iostream>
#include <cmath>
using namespace std;
int main() {
float a = 3.14;
float b = 3.14 - 1e-8;
if (fabs(a - b) < 1e-6) {
cout << "a is almost equal to b" << endl;
} else {
cout << "a is different from b" << endl;
}
return 0;
}
这段代码演示了如何使用 fabs 函数来判断两个浮点数的差值是否小于某个很小的值,以达到判断这两个浮点数是否相等的目的。
右移操作
在C语言中,整数的右移操作 (a >> 2) 与除法操作 (a/4) 结果是一致的吗?
这两者之间的差异在于处理负数的方式以及舍入方式。对于正数,右移操作 a >> 2
和除法操作 a / 4
的结果是一致的,因为它们都会向零舍入。但对于负数,右移操作和除法操作可能会有不同的结果,因为右移操作会向上舍入。
-
右移操作
a >> 2
:- 右移操作是按位操作,将二进制表示的整数向右移动指定的位数。如果整数
a
是正数,则右移操作会将其向下舍入
,即直接丢弃最低的2位。 - 如果整数
a
是负数,则右移操作会将其向上舍入
(即进一位),这可能导致与除法操作不一致的结果。
- 右移操作是按位操作,将二进制表示的整数向右移动指定的位数。如果整数
-
除法操作
a / 4
:- 除法操作是普通的整数除法。它将整数
a
除以4,并返回商。在C中,如果a
是正数,则除法操作会向零舍入
,即截断小数部分。 - 如果整数
a
是负数,则除法操作会保持向零舍入的方式。
- 除法操作是普通的整数除法。它将整数
例如,考虑 a
为-7 的情况:
a >> 2
结果为 -2(向上舍入)。a / 4
结果为 -1(向零舍入)。
总的来说,对于正数,右移操作和除法操作通常是一致的,但在涉及负数的情况下,可能会有不同的行为。在编程中,要根据具体需求选择适当的操作。如果需要一致的行为,可以使用适当的修正或条件检查来确保结果一致。
linux上的环境设置
如何在Visual Studio中调试代码
- 主要是“调试”-》"窗口"中的局部变量,内存1,堆栈调试之类的。
- 观察窗口的格式说明符
- VS中监视窗口,即时窗口和输出窗口的使用
其他
- 搜索的使用
- TODO注释
//Todo something
,view(视图)-》Task List(任务列表)进行查看整个工程的todo
C++是如何工作的
编译器如何工作
- 在编译的时候,程序是按照每个.C或.CPP文件单独编译的。
链接器如何工作
- 函数只声明不定义仍可链接成功只要没有被调用。
- 链接冲突“multiple definition”:C++类的声明和类的实现分开写(.hpp和.cpp)
- 一个简单的动态链接库的生成和调用例子
- C编译器、链接器、加载器详解
- https://github.com/meglory/linux-libso-demo
头文件
- 头文件即为在预处理阶段的简单的复制和替换
- 【C语言的编写时,头文件只能有一个吗!不是】
变量地声明和定义
extern int i;//声明但不定义 extern声明不是定义,也不分配存储空间。事实上,它只是说明变量定义在程序的其他地方。
int i;//声明也定义 分配空间
extern double pi = 3.1416;//分配并初始化了存储空间。且只有当extern声明位于函数外部时,才可以含有初始化式。
extern是C++中的关键字,用于声明外部变量或函数。它可以用于在不同的文件中共享变量或函数。
在C++中,当变量或函数被声明为extern时,表明它们是在其他文件中定义的,而不是在当前文件中定义的。这意味着,如果我们想在当前文件中使用这些变量或函数,我们需要在当前文件中声明它们为extern。
例如,如果我们在一个文件中定义了一个全局变量,而另一个文件需要使用这个变量,我们可以在另一个文件中使用extern关键字来声明这个变量:
// File1.cpp
int globalVar = 10;
// File2.cpp
extern int globalVar; // 声明全局变量
int main() {
cout << globalVar; // 输出10
return 0;
}
同样的,如果我们在一个文件中定义了一个函数,而另一个文件需要调用这个函数,我们可以在另一个文件中使用extern关键字来声明这个函数:
// File1.cpp
void myFunction() {
cout << "Hello World!";
}
// File2.cpp
extern void myFunction(); // 声明函数
int main() {
myFunction(); // 输出Hello World!
return 0;
}
总之,extern关键字的作用是告诉编译器某个变量或函数是在其他文件中定义的,以便在当前文件中使用。
函数调用过程的入栈
- 函数的参数按照从右向左入栈(这样第一个在上边)
- 函数中临时变量,按照定义顺序入栈
- 形参在调用时分配存储单元,调用结束释放
- 参数非指针时,形参重新复制数值,不会改变实参的值
命令行执行exe时的多个参数转为数组 C语言中 int main(int argc,char *argv[])
指针+引用
- 指针是一个整数, 一种存储内存地址的数字。引用int& 是别名。
- 引用使用方法
void foo(int& a){a++;} {int a=5;foo(a);}
- C++函数返回值为引用(&): 若函数的返回值为引用(&),则编译器就不为返回值创建临时变量了。直接返回那个变量的引用。所以千万不要返回临时变量的引用(比如函数中创建在栈上的int q =10;)
- c++中“引用”的底层实现原理详解:虽然引用
例子
#include<stdio.h>
void swap(int *a, int* b){
int* tmp;
tmp = a;
a = b;
b = tmp;
}
int main(){
int a, b;
a = 32;
b = 16;
swap(&a, &b);printf("%d %d", a ,b ); // 32 , 16
return 0;
}
- 指针只是两个数值,参数传递时放入寄存器rsi和rdi
#include <iostream>
using namespace std;
void fun(int *a, int *b) {
int x = *a;
*a = *b;
*b = x;
cout << *a << " " << *b << endl;
}
int main() {
int x = 1, y = 2;
fun(&x, &y);
cout << x << " " << y << endl;
return 0;
}
C++类,类与结构体
-
类
class A{};
和结构体struct A{};
没有什么区别,只是类的默认成员是私有的。 -
C++笔记-ClassA a和ClassA a()的区别与联系 https://blog.csdn.net/qq78442761/article/details/100651081
C++中的静态(static)
- 静态 static 停滞静止的意思,猜想其有关的功能应该是保持常量 或者 具有某种唯一性
- 防止跨编译单元链接,同时具有唯一全局的性质(在头文件中声明 static 变量时,这个变量会具有头文件所在编译单元(源文件)的作用域,即每个包含该头文件的源文件都会有一个独立的 static 变量实例,而这不是 static 变量的本意。)
- 当变量声明为static时,空间将在程序的生命周期内分配,其被存放在在全局数据区(若定义在函数内,函数退出后仍存在,但是无法被使用)。
- 类成员变量:不能在类声明中初始化,需要定义类从属
int A::x;
(类外),类外初始化A::x = 3;
e.g.float Student::score =10.0;
ISO C++ forbids in-class initialization of non-const static member ‘Solution::total’ - 静态成员函数:无法访问非static的成员,但可以被非static函数访问。this为实例指针,
static与类绑定(用::访问)
所以也不能用。
extern 此(全局)变量或函数是在其他文件中。extern int a
,关于其他见上边的变量地声明和定义
const — 不变承诺( 声明常量指针和指针常量 )
-
类似static, 防止跨编译单元链接,同时具有唯一全局的性质
-
不变承诺
void foo(const int a)
保证传入的参数在函数中不被修改(跳出函数后还正常) -
不变承诺
void foo const ( int a)
保证foo不修改参数 -
声明同时初始化赋值
-
const能被this指针->访问(this是一个 const 指针)
-
const成员函数:无法访问非const的成员,但可以被非const函数访问。不能改变类中不被mutable修饰的成员。
-
const和* “复合”:先看const左侧再看const右侧,
const int*
指向的值不能改变,int* const
地址不能改变(int* const b= &a;
) -
常量指针和右值有关 指针指向一个实体常量,这个实体常量所放置的常数是不能改变的,但是这个指针可以指向不同的实体常量
inline — 内联(类似宏定义复制,但在编译时)
- 内联函数 在编译时直接类似宏定义将执行代码复制过来减去调用加快运行速度(
#define MAX(a,b) ((a)>(b)?(a):(b)) /*这些括号防止歧义*/
宏定义是预编译直接替换所以有时会有差错,且无类型检查) - 如果两个文件都引用同一个头文件会链接时会出现重复函数定义错误,也可用inline标注
final override
- override在重写父类函数时进行校验,父类没有函数则会报错。
- final类无法继承,final函数无法重写
C++ 构造与析构函数
c++的构造函数
- 默认构造函数
class Pic {
public:
const int a= 3;
Mat A;
public:
Pic() {};
void setA() { A = imread("C:/Users/admin/Pictures/mask.jpg"); };
};
int main() {
Pic* mypic = new Pic();
}
- 初始化构造函数
- 拷贝构造函数 :复制构造
A a;A b(a);
或A a;A b=a;
Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}// https://www.runoob.com/cplusplus/cpp-copy-constructor.html
- 移动构造函数:右值引用的作用是实现移动构造,通过
A a;A b(std::move(a));
。
C++中左值和右值的理解,首先,让我们避开那些正式的定义。在C++中,一个左值是指向一个指定内存的东西。另一方面,右值就是不指向任何地方的东西。通常来说,右值是暂时和短命的,而左值则活的很久,因为他们以变量的形式(variable)存在。我们可以将左值看作为容器(container)而将右值看做容器中的事物。如果容器消失了,容器中的事物也就自然就无法存在了。移动构造函数
容器(containers)
字符串
const , mutable
enum
C++的三元操作符
创建并初始化C++对象
C++ new关键字(在堆上分配返回地址 malloc)
// https://learn.microsoft.com/en-us/cpp/cpp/new-operator-cpp?view=msvc-170
char (*pchar)[10] = new char[dim][10];
delete [] pchar;// delete[] 元素逆序销毁
- new的过程会分配内存和构造,allocator可以实现只分配内存
- new返回“新的”
int main() {
std::cout << "Hello, World!" << std::endl;
int a[]={1,2};
std::vector<ListNode*> b;
for(int i=0;i<(sizeof(a) / sizeof(a[0]) );i++){
ListNode* node = new ListNode(a[i]);
b.push_back(node);
}
// int a[]={1,2};
// std::vector<ListNode*> b;
// for(int i=0;i<(sizeof(a) / sizeof(a[0]) );i++){
// ListNode node = ListNode(a[i]);//创建的node还是原来的地址,最终输出为2 2
// b.push_back(&node);
// }
for(int i=0;i<(sizeof(a) / sizeof(a[0]) )-1;i++){
b[i]->next =b[i+1];
std::cout<< b[i]->val << b[i+1]->val <<std::endl;
}
C++隐式转换
C++运算符及其重载(java中为什么使用equals())
bool operator ==(){
return true;
}
- 只有C++预定义的操作符集中的操作符才可以被重载
- opencv的VideoCapture的 >>
- 断点,按F11跟进去找到实现:
C++的this关键字
- this相当于一个自带的成员变量
- this指向当前实例(有点像self)的指针。
c++中什么时候用到this
- 在C++中,
this
是一个指向当前对象的指针。它用于区分成员变量和方法参数中的同名变量,并且可以用于返回当前对象的引用。通常情况下,在类的成员函数中,如果需要访问当前对象的成员变量或者调用当前对象的方法,就需要使用this
指针。
- 在成员函数中访问当前对象的成员变量:
class MyClass {
public:
void setX(int x) {
this->x = x;
}
private:
int x;
};
- 在成员函数中返回当前对象的引用:
class MyClass {
public:
MyClass& doSomething() {
// do something
return *this;
}
};
- 在构造函数中初始化成员变量:
class MyClass {
public:
MyClass(int x) : x(x) {}
private:
int x;
};
- 在上述例子中,
this
指针始终指向当前对象的地址。
C++的对象生存期(栈作用域生存期)
C++的智能指针
include<memory>
- unique_ptr
std::unique_ptr<int> up = std::unique_ptr<int>(new int(1));
,出作用域自动销毁,拷贝使用std::move - shared_ptr 多个指针指向同一个值
std::shared_ptr<int> foo = std::make_shared<int> (1);
orstd::shared_ptr<int> foo2 (new int(1));
- 智能指针的最简单初始化形式即为 用尖括号传入类型,圆括号传入指针。对于shared_ptr还能new_name= old_name
#include "boost/shared_ptr.hpp"
#include <cassert>
class A {
boost::shared_ptr<int> no_;
public:
A(boost::shared_ptr<int> no) : no_(no) {}
void value(int i) {
*no_=i;
}
};
class B {
boost::shared_ptr<int> no_;
public:
B(boost::shared_ptr<int> no) : no_(no) {}
int value() const {
return *no_;
}
};
int main() {
boost::shared_ptr<int> temp(new int(14));
A a(temp);
B b(temp);
a.value(28);
assert(b.value()==28);
}
example
- https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr
// https://blog.csdn.net/weixin_44966641/article/details/125472418
// 上面的实现有个比较不优雅的地方,对于我们创建的 builder、config 等指针,我们都需要一一进行 destroy,从而避免内存泄漏。实际上,这里我们可以通过共享指针,来实现自动释放。
template<typename _T>
shared_ptr<_T> make_nvshared(_T *ptr) {
return shared_ptr<_T>(ptr, [](_T* p){p->destroy();});
}
reset()
两个错误用法
不能将一个原始指针直接赋值给一个 std::shared_ptr 智能指针类型
,智能指针类本质是一个模板类,可包装原始指针为智能指针类型并赋值
sp = std::shared_ptr(xx);
#include <iostream>
#include <memory>
class Tmp {
public:
Tmp(){};
Tmp(int a): __a(a){};
~Tmp(){};
virtual void print__a() { std::cout << "value = " << __a << std::endl; }
private:
int __a;
};
int main() {
std::shared_ptr<Tmp> sp; // empty
Tmp *xx = new Tmp(10);
sp = xx; // no match for ‘operator=’ (operand types are ‘std::shared_ptr’ and ‘Tmp*’)
sp->print__a();
return 0;
}
- 在代码中,sp.reset(xx); 出错的原因是
reset 函数要求传入的参数类型必须与 shared_ptr 的模板参数类型相同
。由于 xx 的类型是 Tmp*,而 sp 的模板参数类型是 Nmp,因此会编译错误,无法通过编译。为了解决这个问题,可以将 Tmp 类和 Nmp 类之间建立继承关系,或者使用一些类型转换技巧来进行转换。
#include <iostream>
#include <memory>
class Nmp {
public:
Nmp(){};
Nmp(int a): __a(a){};
~Nmp(){};
virtual void print__a() { std::cout << "value = " << __a << std::endl; }
private:
int __a;
};
class Tmp {
public:
Tmp(){};
Tmp(int a): __a(a){};
~Tmp(){};
virtual void print__a() { std::cout << "value = " << __a << std::endl; }
private:
int __a;
};
int main() {
std::shared_ptr<Nmp> sp; // empty
Tmp *xx = new Tmp(10);
sp.reset(xx); // no matching function for call to ‘std::shared_ptr::reset(Tmp*&)
sp->print__a();
return 0;
}
正确用法
#include <iostream>
#include <memory>
class Tmp {
public:
Tmp(){};
Tmp(int a): __a(a){};
~Tmp(){};
virtual void print__a() { std::cout << "value = " << __a << std::endl; }
private:
int __a;
};
int main() {
std::shared_ptr<Tmp> sp; // empty
Tmp *xx = new Tmp(10);
sp.reset(xx); // no match for ‘operator=’ (operand types are ‘std::shared_ptr’ and ‘Tmp*’)
sp->print__a();
return 0;
}
weak_ptr
std::weak_ptr
主要用于解决 C++ 中的循环引用问题,它可以在不增加对象引用计数的前提下,安全地对对象进行引用和操作。下面是一个使用 std::weak_ptr
的示例代码:
#include <iostream>
#include <vector>
#include <memory>
class Person; // 前置声明
class Car {
public:
Car() { std::cout << "Car Constructor" << std::endl; }
~Car() { std::cout << "Car Destructor" << std::endl; }
std::weak_ptr<Person> owner_; // 持有 Person 的 weak_ptr
};
class Person : public std::enable_shared_from_this<Person> {
public:
Person() {
std::cout << "Person Constructor" << std::endl;
}
~Person() {
std::cout << "Person Destructor" << std::endl;
}
void OwnCar(std::shared_ptr<Car> car) {
car_ = car;
car_->owner_ = shared_from_this(); // 通过 shared_from_this() 构造 weak_ptr 安全持有对象
}
private:
std::shared_ptr<Car> car_;
};
int main() {
{
std::vector<std::shared_ptr<Person>> persons;
std::vector<std::shared_ptr<Car>> cars;
auto person = std::make_shared<Person>();
auto car = std::make_shared<Car>();
persons.push_back(person);
cars.push_back(car);
person->OwnCar(car);
} // 函数结束自动析构
return 0;
}
在上面的示例中,我们定义了一个 Person
类和一个 Car
类,Person
持有 Car
的 std::shared_ptr
,而 Car
持有 Person
的 std::weak_ptr
。由于 std::weak_ptr
不会增加引用计数,因此可以避免出现循环引用的问题。在使用 shared_from_this()
函数返回一个指向自身的 std::shared_ptr
,以构造一个 std::weak_ptr<Person>
,安全地持有对象。当 Person
对象被销毁时,它所持有的 std::shared_ptr<Car>
也会被回收,而 Car
对象则不会被销毁,因为它被 std::weak_ptr<Person>
安全持有。
cg
函数指针
// https://zhuanlan.zhihu.com/p/37306637#%E6%8A%8A%E5%87%BD%E6%95%B0%E4%BD%9C%E4%B8%BA%E5%8F%82%E6%95%B0%E4%BC%A0%E5%85%A5%E5%8F%A6%E4%B8%80%E4%B8%AA%E5%87%BD%E6%95%B0
#include <iostream>
int add(int a, int b){
return a+b;
}
int sub(int a, int b){
return a-b;
}
void func(int e, int d, int(*f)(int a, int b)){ // 这里才是我想说的,
// 传入了一个int型,双参数,返回值为int的函数
std::cout<<f(e,d)<<std::endl;
}
int main()
{
func(2,3,add);
func(2,3,sub);
return 0;
}
设计模型
C++继承+虚函数
继承
-
父类公有成员和保护成员都作为子类的私有成员
class B : private A
-
关于public,protected,private三者继承后的情况是三个依次增强
-
C++
is an inaccessible base of
问题 : class默认的继承方式是私有继承private,而私有继承时基类指针无法指向派生类
#include <iostream>
#include <memory>
class Tmp {
public:
Tmp(){};
Tmp(int a): __a(a){};
~Tmp(){};
virtual void print__a() { std::cout << "value = " << __a << std::endl; }
private:
int __a;
};
class TmpA : public Tmp{
public:
TmpA(int a): __a(a){};
~TmpA(){};
void print__a() { std::cout << "value1 = " << __a << std::endl; }
private:
int __a;
};
int main() {
std::shared_ptr<Tmp> sp; // empty
Tmp *xx = new Tmp(10);
sp.reset(xx); // takes ownership of pointer
sp->print__a();
std::cout << "============11" << std::endl;
Tmp *tt = new TmpA(20);
sp.reset(tt); // takes ownership of pointer
sp->print__a();
std::cout << "============22" << std::endl;
sp.reset(); // deletes managed object
// sp->print__a(); //crash
if (!sp.get()) {
printf("empty\n");
}
return 0;
}
virtual
- 包含一个或多个纯虚拟函数的类被编译器识别为抽象基类。抽象基类不能被实例化,一般用于继承。
- 虚函数的使用(重写 覆盖的几个函数必须函数名、参数、返回值都相同):通过基类指针或引用间接指向派生类子类型
Base *b = new Derived;(Base的函数标注virtual)
基类指针指向派生类对象
- 即基类动态地变为子类,并且能调用子类的函数(必须是重写父类的虚函数)智能指针时的例子:声明时为基类,赋值为子类
- 以上多态实现原理
虚函数表实在对象构造之后才建立的
,所以构造函数不可能是虚函数,且不能在构造函数内调用虚函数
若析构函数不是虚函数,delete 时,只有基类会被释放,而子类没有释放,存在内存泄漏的隐患。- 纯虚函数(只依靠子类实现功能):virtual void func()=0
虚拟继承class B : public virtual A{}
-
在多继承下,虚继承就是为了解决菱形继承中,B,C都继承了A,D继承了B,C,那么D关于 A的引用只有一次
-
动态链接是多态性
-
菱形继承:多次拷贝,且
cout<<d->a;
报错request for member ‘a’ is ambiguous -
虚拟继承:只有一个共享备份
#include <iostream>
using namespace std;
class A{
public:
int a;
virtual void func(){cout<<"A";}
};
class B:virtual public A{
public:
int b;
void func(){cout<<"B";}
};
class C:virtual public A{
public:
int c;
void func(){cout<<"C";}
};
class D: public B,public C{
public:
int d;
void func(){cout<<"D";}
};
int main(){
D *d = new D();
d->func();
d->B::a =1;
cout<<d->a;
d->C::a = -1;
cout<<d->a;
d->a = 2;
cout<<d->a;
}
静态类型 动态类型
- 静态类型:在编译器能确定的类型
- 动态类型:在运行期确定所指的类型
编译期多态和运行时多态
- 运行时多态通过以上虚拟继承实现
- 编译期多态通过模板特例化和函数重载解析实现的。以不同的模板参数特例化导致调用不同的函数。
类型及转换
explicit 防止函数中发生隐式转换
在C++中,explicit
是一个关键字,用于修饰构造函数,指示编译器只能使用显式构造函数调用,而不能进行隐式类型转换。具体来说,explicit
关键字可以用于单参数构造函数,防止编译器将该构造函数用于隐式类型转换。例如:
class MyClass {
public:
explicit MyClass(int x) { ... }
};
int main() {
MyClass obj = 42; // 这里会编译错误,因为构造函数是 explicit 的
MyClass obj2(42); // 这里是正确的,因为使用了显式构造函数调用
return 0;
}
在上面的代码中,MyClass
类的构造函数被标记为explicit
,因此编译器不允许将整数值隐式转换为MyClass
类型。如果我们尝试使用隐式构造函数调用,编译器会报错。但是,我们仍然可以使用显式构造函数调用来创建MyClass
对象。
- opencv的VideoCapture类中的函数例子
auto 自动推断该类型
// start fun timer
auto start_of_time = high_resolution_clock::now();
dynamic_cast
B* p= new D;
(D*) P;//基础类不安全地转为派生类
- D* p1 = dynamic_cast<D*>( p );
- 能够将基类的指针转换为派生类的指针或者引用。
转换失败,返回空地址
。(多态)
static_cast相当于传统的C语言里的强制转换不需要虚函数
- const_cast(用来移除 const), static_cast(主要用于把基类指针转换为派生类指针), reinterpret_cast 和 dynamic_cast
C++运算符中的隐式类型转换规则
//https://stackoverflow.com/questions/5563000/implicit-type-conversion-rules-in-c-operators
int + float => float + float = float
int * float => float * float = float
float * int => float * float = float
int / float => float / float = float
float / int => float / float = float
int / int = int
int ^ float => <compiler error>
类型转换的例子
在C++中,类型转换(Type Casting)是一种将一个数据类型的值转换为另一个数据类型的过程。C++提供了几种不同的类型转换操作符,可以根据需要执行不同类型的转换。以下是C++中常见的类型转换方法:
- 隐式类型转换(Implicit Type Conversion):
这种类型的转换是由编译器自动执行的,无需显式操作符。它通常发生在以下情况:- 类型提升:当不同数据类型的表达式进行运算时,较低精度的数据类型会自动转换为较高精度的数据类型,以确保精度不会丢失。
- 类型扩展:当将较小的整数类型(如
int
)分配给较大的整数类型(如long
)时,会发生类型扩展。
int a = 10;
long b = a; // 隐式类型转换,int 转为 long
-
显式类型转换(Explicit Type Conversion):
显式类型转换需要使用C++中的类型转换操作符,以明确告诉编译器要执行的类型转换。有三种常见的显式类型转换操作符:- C 风格类型转换(C-style casting):使用括号和目标类型进行转换。
- C++ 风格的类型转换(C++ casting):C++引入了四种类型转换操作符,用于不同的情况。这些操作符是
static_cast
、dynamic_cast
、const_cast
和reinterpret_cast
。 - 函数式类型转换:C++11引入了函数式类型转换操作符,使用
<type>()
形式进行转换。
下面是这些类型转换操作符的示例:
// C 风格类型转换
double x = 3.14;
int y = (int)x;
// C++ 风格类型转换 - static_cast
int i = 10;
double d = static_cast<double>(i);
// C++ 风格类型转换 - dynamic_cast (通常用于多态类之间的转换)
Derived* derived_ptr = dynamic_cast<Derived*>(base_ptr);
// C++ 风格类型转换 - const_cast (用于删除const性质,慎用)
const int j = 20;
int k = const_cast<int>(j);
// C++ 风格类型转换 - reinterpret_cast (进行底层的二进制转换,慎用)
int* p = reinterpret_cast<int*>(0x7FFF);
需要注意的是,不同的类型转换操作符有不同的用途和限制,应谨慎选择合适的类型转换方式,以避免潜在的错误和问题。在使用类型转换时,最好确保转换是安全和合理的,以维护代码的可维护性和可读性。
- 使用的注意事项:【C++】类型转换(静态转换 动态转换 常量转换 重新解释转换):static_cast适用于上行转换,dynamic_cast适用于下行转换。
static_cast进行上行转换(把派生类的指针或引用转换成基类表示)是安全的
基类指针步长相对于派生类指针步长来说较小,并不会超出派生类范围访问其他数据
static_cast进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
基类指针步长相对于派生类指针步长来说较小,使用派生类指针访问基类对象,可能会超出范围
在进行下行转换时(不安全,子类指针可能会超出基类对象),dynamic_cast具有类型检查的功能,比static_cast更安全
volatile 禁用优化 多任务的情况下使用
组合
-
组合是C++中一种面向对象编程的概念,它是指一个类包含另一个类的对象作为它的成员变量。这种关系称为“has-a”关系,即一个对象“has-a”另一个对象。
-
组合的实现方式是在一个类中定义另一个类的对象作为它的成员变量,然后在类的构造函数中初始化这个成员变量。例如:
class Engine {
public:
void start() {
// 启动引擎
}
};
class Car {
private:
Engine engine;
public:
Car() : engine() {
// 初始化引擎
}
void start() {
engine.start();
}
};
在这个例子中,Car类包含一个Engine类的对象作为它的成员变量。Car类的构造函数初始化了这个成员变量,然后在Car类的start()函数中调用了Engine类的start()函数来启动引擎。
组合的优点是可以将多个类组合成一个更大的类,从而更好地组织和管理代码。另外,组合还可以通过封装来隐藏成员变量的实现细节,提高代码的可维护性和可重用性。
class head
{};
calss human
{
private:
head h; // 数据成员对象表示的组合关系 https://www.jianshu.com/p/5ba247507f7d
}
命名空间
模板
- 模板不真实存在,除非我们调用模板,所以不调用的模板中有错误也能通过编译(某些编译器)。
- https://en.cppreference.com/w/cpp/language/templates
- C++20: Concept详解以及个人理解
模板函数
template <class 形参名,class 形参名,......> 返回类型 函数名(参数列表) {函数体}
template<typename T>
int foo(const T& a, const T& b) {if (a< b) {return 1; }return 0; }# 好像只是多了一个定义T的头
foo(100,101);//调用会自动推断类型
- class 和 typename都可以:一般情况下typename和class可以互换。表示某标识符是类型的时候用只能用typename而不能用class
template<typename T> //或 template<class T>
void myswap(T&a, T&b)
{
T c = a;
a = b;
b = c;
}
int main()
{
int a = 1, b = 2;
myswap(a,b);
cout <<a << b << endl;
}
特化模板Template specialization
当我们需要为特定类型或值提供不同的实现时,可以使用特化模板来覆盖通用实现。下面是一个特化模板的示例,用于计算数组中元素的平均值:
#include <iostream>
using namespace std;
// 通用模板
template<typename T>
T average(T arr[], int size)
{
T sum = 0;
for(int i=0; i<size; i++)
sum += arr[i];
return sum/size;
}
// 针对字符数组的特化模板
template<>
char average(char arr[], int size)
{
char sum = 'a';
for(int i=0; i<size; i++)
sum += arr[i];
return sum/size;
}
int main()
{
int arr1[5] = {1, 2, 3, 4, 5};
double arr2[5] = {1.1, 2.2, 3.3, 4.4, 5.5};
char arr3[5] = {'a', 'b', 'c', 'd', 'e'};
cout << "Average of arr1: " << average(arr1, 5) << endl; // 输出:3
cout << "Average of arr2: " << average(arr2, 5) << endl; // 输出:3.3
cout << "Average of arr3: " << average(arr3, 5) << endl; // 输出:'c'
return 0;
}
在上面的代码中,我们定义了一个通用模板函数 average
来计算任意类型的数组的平均值。然后,我们特化了该模板函数,为字符数组提供了一个自定义实现。
由于字符数组具有不同的数据类型,因此需要使用特化模板来覆盖通用实现。在我们的示例中,特化模板函数计算字符数组元素的 ASCII 值的平均值,并返回其对应的字符。
类模板
template<class 形参名,class 形参名,…> class 类名
{ ... };
template <typename T>
class Parent{
public:
Parent(T p)
{
this->p = p;
}
private:
T p;
};
- std::vector<> 类的实现
特化模板类
template <typename T>
class MyTemplateClass {
public:
void doSomething() { std::cout << "I'm a generic template class." << std::endl; }
};
// 特化模板类,当我们需要使用MyTemplateClass来处理整数时,编译器将优先选择特化版本
template <>
class MyTemplateClass<int> {
public:
void doSomething() { std::cout << "I'm a specialized template class for int." << std::endl; }
};
类特化模板的继承
// 例子来源 https://stackoverflow.com/questions/213761/what-are-some-uses-of-template-template-parameters
template <class T> class Tensor
template <class T> class TensorGPU : public Tensor<T>
auto
vector<int> v={1,2,3,4};
for(auto i:v)
cout<<i;#1234
函数重写 重载
# 重写
class A{ void fun(int a,char b,double c){}}
class B:public A{ void fun(int a,char b,double c){}}
# 重载:一个函数名称,对应多个函数
class A{ void fun(int a){}; void fun(int a,char b){}; void fun(int a,char b,double c){};}
USING
C++ 标准模板库(STL)
段错误 segmentation-faultL: 当一段代码尝试在内存或释放的内存块中的只读位置执行读写操作时,称为分段错误
- https://www.geeksforgeeks.org/segmentation-fault-c-cpp/
- https://www.geeksforgeeks.org/storage-for-strings-in-c/
- https://hackingcpp.com/cpp/cheat_sheets.html
CG
- 如何在不适用额外空间情况下交换两个整数?
x=x+y;y=x-y;x=x-y;
- 内存泄漏(忘记free内存导致无法被使用)的避免? 将基类的析构函数声明为虚函数
delete p;
时delete指针时先释放子类,后释放父类;检测工具CRT库或Valgrind。 - C++ typedef typename 作用: typename 防止歧义
- C++ isLetter函数代码示例 if( isLetter(p[0]) )
- C++程序编写注释规范
- if (isdigit(a))
// http://c.biancheng.net/view/2228.html
// 静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
#include <iostream>
using namespace std;
class Student{
public:
Student(char *name, int age, float score);
void show();
public: //声明静态成员函数
static int getTotal();
static float getPoints();
private:
static int m_total; //总人数
static float m_points; //总成绩
private:
char *m_name;
int m_age;
float m_score;
};
int Student::m_total = 0;
float Student::m_points = 0.0;
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
m_total++;
m_points += score;
}
void Student::show(){
cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
//定义静态成员函数
int Student::getTotal(){
return m_total;
}
float Student::getPoints(){
return m_points;
}
int main(){
(new Student("小明", 15, 90.6)) -> show();
(new Student("李磊", 16, 80.5)) -> show();
(new Student("张华", 16, 99.0)) -> show();
(new Student("王康", 14, 60.8)) -> show();
int total = Student::getTotal();
float points = Student::getPoints();
cout<<"当前共有"<<total<<"名学生,总成绩是"<<points<<",平均分是"<<points/total<<endl;
return 0;
}
memset
是一个 C 语言库函数,用于将一块内存空间初始化为指定的值。其函数原型如下:void *memset(void *s, int c, size_t n);
其中,s
是指向要初始化的内存块的指针;c
是要设置的值,通常为 0 或 -1;n
是要设置的字节数。该函数返回指向s
的指针。
template CG
template template parameter
// https://stackoverflow.com/questions/213761/what-are-some-uses-of-template-template-parameters
#include<stdio.h>
#include <iostream>
using namespace std;
template<class A>
class B
{
public:
A* a;
int GetInt() { return a->dummy; }
};
template< template<class> class B>
class A
{
public:
A() : dummy(3) { b.a = this; }
B<A> b;
int dummy;
};
class AInstance : public A<B> //happy
{
public:
void Print() { std::cout << b.GetInt(); }
};
int main()
{
std::cout << "hello";
AInstance test;
test.Print();
}
https://github1s.com/sirotenko/cudacnn/blob/HEAD/include/layer.hpp#L172-L173
template <template <class> class TT, class T, class TF>
class CLayerT: public Layer<TT, T>
模板的多态
- 关于C++面向对象编程,下面说法错误的是:
A父类可以调用子类方法
B子类类型不可以作为父类的模板参数
C父类类型可以作为子类的模板参数
D 子类可以调用父类方法
restrict
在C++中,restrict
是一个关键字,用于向编译器发出一个提示,表示指针指向的内存区域是不重叠的,从而帮助编译器对内存访问进行优化。
通常情况下,编译器必须考虑到在相同作用域内多个指针可能会指向相同的内存区域,因此不能对它们的操作进行一些特殊的优化措施,比如指令重排和寄存器的使用等。但是当使用restrict
关键字来修饰指针时,编译器就可以认为这些指针所指向的内存区域不会重叠,从而可以对它们的操作进行一些更高效的优化。
需要注意的是,restrict
关键字只能用于指针,而不能用于其他类型的变量。它通常用于在编写高性能代码时,向编译器提供一些关于指针使用的附加信息,以便进行更高效的代码优化。然而,使用restrict
关键字也需要非常小心,因为如果错误地使用了restrict
关键字,可能会导致程序的行为变得不可预测。
c++中嵌入汇编语言
在C++中,您可以嵌入汇编语言代码来执行底层的操作,通常使用asm
或__asm
关键字,具体取决于您的编译器。以下是一个简单的示例,演示如何在C++中嵌入汇编语言代码:
#include <iostream>
int main() {
int a = 5;
int b = 10;
int result;
// 嵌入汇编代码
__asm {
mov eax, a // 将a加载到EAX寄存器
add eax, b // 将b加到EAX寄存器
mov result, eax // 将EAX寄存器中的结果保存到result变量
}
std::cout << "Result: " << result << std::endl;
return 0;
}
上面的示例演示了如何在C++中使用嵌入汇编语言来执行加法操作。请注意,嵌入汇编语言通常是特定于编译器的,因此您需要查看您使用的编译器的文档以了解如何正确使用嵌入汇编语言。此外,嵌入汇编语言在现代C++中并不常见,因为C++提供了更高级的方式来处理底层操作。只有在需要极高性能或与硬件密切相关的情况下才建议使用嵌入汇编语言。
异常
在 C++ 中,可以使用 catch
语句来捕获任意类型的对象。具体形式如下:
#include <iostream>
int main() {
try {
int a = 1/0; // 可能会引发异常的代码块
} catch (...) {
// 捕获任意类型的异常对象的处理代码
std::cout << "An exception occurred" << std::endl;
}
return 0;
}
使用 catch (...)
可以捕获所有类型的异常对象,而无需指定具体的异常类型。在捕获到异常后,可以在 catch
块中对异常进行处理。请注意,捕获任意类型的异常对象时,无法访问具体的异常类型信息,因此在处理时需要谨慎操作。