C++基础

时间:2024-06-03 18:58:38

目录

1 带形参默认值的函数

2 关键字

inline

volatile

3 函数重载

4 int main(int argc, char **argv)

5 const

用法

1 声明常量变量:

2 常量引用:

3 常量成员函数:

4 常量指针和指向常量的指针:

5 常量引用

6 const修饰的量常出现的错误

6 指针

函数指针

指针函数

7 引用

引用和指针的区别

传值传引用传指针

左值引用右值引用

8 const 指针 引用

8 new、delete和malloc、free


转载文章:C++基础(二) —— 参数_c++ 参数-****博客 

1 带形参默认值的函数

带有形参默认值的函数是指在函数声明或定义中为一个或多个参数提供默认值,以便在调用函数时可以选择省略这些参数。当省略具有默认值的参数时,函数将使用预先定义的默认值来执行。
需要注意:
1 给默认值的时候,从右向左给
2 定义出可以给形参默认值,声明也可以给形参默认值
3 形参给默认值的时候,不管是定义处给,还是声明处给,形参默认值只能出现一次

2 关键字

inline

关于inline关键字,它用于向编译器建议将函数内联展开,以减少函数调用的开销。它告诉编译器将函数的代码插入到调用函数的地方,而不是生成函数调用的代码。这可以提高程序的性能,尤其是在频繁调用的小型函数中。

然而,是否真正将函数内联展开取决于编译器和构建配置。虽然inline关键字可以建议函数内联,但编译器可以根据自身的优化策略决定是否将函数内联。对于Debug版本,编译器通常更关注调试能力和代码的可读性,因此可能不会强制进行内联展开。而在Release版本中,编译器更倾向于进行优化,包括函数内联。

需要注意的是,这些行为并非C++标准规定,而是编译器实现和构建配置的特定行为。不同的编译器和构建系统可能有不同的策略和行为。因此,在不同的环境中,inline关键字的实际行为可能会有所不同。

volatile

volatile关键字用于告诉编译器该变量可能会被外部因素修改(多线程环境),需要在每次访问变量时都重新从内存中读取该变量的值,而不是使用已经缓存的值。
因此,inline是为了提高程序的执行效率,而volatile则是为了保证程序的正确性。

#include <iostream>
#include <thread>
#include <chrono>

volatile bool is_running = true;

void stop_running() {
    is_running = false;
}

void do_something() {
    while (is_running) {
        std::cout << "Doing something..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    std::cout << "Stopped doing something." << std::endl;
}

int main() {
    std::thread t1(do_something);
    std::this_thread::sleep_for(std::chrono::seconds(3));
    stop_running();
    t1.join();
    return 0;
}

在上述代码中,主线程会启动一个子线程执行do_something()函数,函数中的循环条件是is_running变量,该变量被声明为volatile bool类型,保证了子线程对该变量的修改会立即对主线程可见,从而实现了主线程和子线程之间的同步。但不保证原子操作

3 函数重载

一组函数,其中函数名相同,参数列表的个数或者类型不同为重载。
const或者volatile情况:

void func(int *a) {}  // int
void func(int *const a) {}  
int main()
{
    int a = 10;
    const int b = 10;

    cout << typeid(a).name() << endl;
    cout << typeid(b).name() << endl;

    return 0;
}

// const不会改变类型还是int 造成函数重定义
// 加入指针后 则可以被重载 不加指针 相当于函数重定义
多态

静态(编译时期)的多态:函数重载
动态(运行时期)的多态:虚函数

4 int main(int argc, char **argv)

从C语言声明的角度分析

  • char* argv[]声明一个数组argv,该数组保存多个指向char类型的指针
  • char **argv声明argv为指向(指向 char 类型的指针)的指针
  • 换句话说,一个是数组类型的声明,一个是指针类型的声明

从内存管理的角度分析

  • 在声明数组的时候,会在内存中分配连续的数组空间
  • 在分配指针的时候,不会分配上述空间

5 const

const必须初始化,叫常量。
C++中,所有出现const常量名字的地方,都被常量的初始化替换了。
const修饰的变量不能再作为左值,即初始化完成后,值不能被修改。

用法

1 声明常量变量:

const int x = 5;等于int const x = 5;
x被声明为一个常量变量,其值为5。常量必须初始化,且一旦被初始化,常量变量的值在程序执行期间是不能被修改的。

2 常量引用:

const int& y = x;
y是一个常量引用,它引用了常量变量x。常量引用允许通过引用访问一个值,但不允许修改该值。

3 常量成员函数:

class MyClass {
    int x;
public:
    void func() const;
};

void MyClass::func() const {
    // 不能修改成员变量x
}

在类中,使用const关键字可以将成员函数声明为常量成员函数。常量成员函数承诺不会修改类的成员变量。

4 常量指针和指向常量的指针:


const修饰的是离它最近的类型

const int* ptr1; // ptr1是指向常量的指针,指向的值不能通过ptr1修改
int* const ptr2; // ptr2是常量指针,不能通过ptr2修改指针指向的地址
const int* const ptr3; // ptr3是指向常量的常量指针,既不能修改指针地址,也不能修改指向的值

这些是指针的常量相关的声明方式,通过在类型前加上const来声明指向常量的指针或常量指针。

5 常量引用

int qq = 1;
const int& rqq = qq;


rqq是qq的别名,rqq不能修改qq,但qq可以让rqq跟着变。

6 const修饰的量常出现的错误

1 常量不能再作为左值

int main() {
    const int a = 10;
    a = 20;//直接修改常量的值
    return 0;
}

2 不能把常量的地址泄露给一个普通的指针或者普通的引用变量

int main() {
    const int a = 10;
    int *p = &a;//常量的地址泄露给一个普通的指针或者普通的引用变量==
    //应改为 const int *p = &a;
    return 0;
}

6 指针

函数指针

一般正常用法:

#include <iostream>

// 定义一个回调函数类型
typedef void (*CallbackFunction)(int);

// 函数接受一个回调函数作为参数
void performOperation(int data, CallbackFunction callback) {
    // 执行一些操作
    // ...
    // 调用回调函数
    callback(data);
}

// 实际的回调函数
void callbackFunction(int data) {
    std::cout << "Callback function called with data: " << data << std::endl;
}

int main() {
    // 使用回调机制,将回调函数传递给performOperation函数
    performOperation(42, callbackFunction);//函数名就是指针,当然为了代码可读性也可以使用&显式转换一下

    return 0;
}

ROS中的隐式用法:

void imu_cbk(const sensor_msgs::Imu::ConstPtr &msg_in)
{
    mtx_buffer.lock();
    imu_buffer.push_back(msg);
    mtx_buffer.unlock();
}

ros::Subscriber sub_imu = nh.subscribe(imu_topic, 200000, imu_cbk);

imu_cbk是作为回调函数传递给subscribe函数的,它在这种情况下被隐式地转换为函数指针类型。当然,现在更加建议使用functional对象而不是函数指针。

指针函数

指针函数是一个返回指针的函数,它的返回值类型是一个指针。

// 定义一个指针函数,返回一个int类型的指针
int* createIntPointer() {
    int* ptr = new int(42);
    return ptr;
}

int main() {
    // 调用指针函数,获取一个int类型的指针
    int* ptr = createIntPointer();
    // 使用指针操作符*解引用指针,获取指针所指向的值
    std::cout << "Value: " << *ptr << std::endl;
    delete ptr;
    return 0;
}

7 引用

引用和指针的区别

不同点:
初始化:引用在声明时必须进行初始化,并且一旦初始化后,它始终引用同一个对象,无法更改引用的目标。指针可以在声明时进行初始化,也可以在之后指向其他对象。指针可以不用初始化。
空值:引用不能为null或空值,它必须引用一个有效的对象。指针可以是空指针(nullptr),表示指向空值或无效的内存地址。
空间要求:引用不会占用额外的内存空间,它只是对象的别名。指针需要额外的内存来存储指针变量本身以及指向的对象的地址。
安全性:引用在使用时不需要进行空值检查,因为它总是引用有效的对象。指针在使用之前需要进行空指针检查,以避免访问无效的内存地址。

相同点:
定义一个引用变量,和定义一个指针变量,其汇编指令是一模一样的。
通过引用变量修改所引用内存的值,和通过指针解引用修改指针指向的内存的值,其底层指令也是一模一样的。

传值传引用传指针

传值:将实参的值复制到形参中。在函数内部,对形参的操作不会影响到实参。
传引用:将实参的引用传递给形参,在函数内部对形参的操作会影响到实参。
传指针:将实参的指针传递给形参,在函数内部通过指针访问实参。

它们之间的区别如下:
传值:传值方式会涉及到复制参数,这会占用额外的空间,并且对于复杂的数据类型(如结构体或类),会导致性能下降。但是,传值方式对实参不会有任何影响。
传引用:传引用方式不会涉及到复制参数,因此可以提高性能。而且,函数内部对形参的修改会影响到实参,因此传引用可以用于修改实参的值。
传指针:传指针方式也不会涉及到复制参数,因此也可以提高性能。而且,在需要修改实参值的情况下,也可以通过指针来修改实参的值。不过,传指针需要额外考虑指针是否为空的情况,并且需要使用*和->运算符来访问实参的值。
总的来说,对于简单的数据类型,传值方式比较方便和安全;对于复杂的数据类型,传引用或传指针方式更常用,因为它们可以提高性能并且可以修改实参。

左值引用右值引用

左值引用符:&
右值引用符:&&

左值&右值:
左值:一般指的是一个对象,或者说是一个持久的值,例如赋值的返回值、下标操作、解引用以及前置递增等。
右值:一个短暂的值,比如一个表达式的求值结果、函数返回值以及一个字面值等。

右值引用作用:
​ 为了支持移动操作(包括移动构造函数和移动赋值函数),C++才引入了一种新的引用类型——右值引用,可以*接管右值引用的对象内容。

区别:
绑定的对象(引用的对象)不同,左值引用绑定的是返回左值引用的函数、赋值、下标、解引用、前置递增递减
左值持久,右值短暂,右值只能绑定到临时对象,所引用的对象将要销毁或该对象没有其他用户
使用右值引用的代码可以*的接管所引用对象的内容

others:
左值引用不能绑定到右值对象上,右值引用也不能绑定到左值对象上。
由于右值引用只能绑定到右值对象上,而右值对象又是短暂的、即将销毁的。也就是说右值引用有一个重要性质:只能绑定到即将销毁的对象上。

左值、右值引用的几个例子:
int i = 42;//如前所述,i是一个左值对象
int &r = i;//正确,左值引用绑定到左值对象i
int &&rr = i;//错误,右值引用绑定左值对象
int &r2 = i * 42;//错误,如前所述i*42是临时变量,是右值,而&r2是左值引用
int &&rr2 = i * 42;//正确,右值引用绑定右值对象
注意:以上绑定规则有一个例外,如果左值引用是const类型的,则其可以绑定到右值对象上。

const int &r3 = i * 42;//正确,我们可以将一个const的引用绑定到一个右值对象上
对于一个左值,若想使用其右值引用,我们可以用move函数:

int &&rr3 = std::move(rr1);//正确,显式使用rr1的右值引用

8 const 指针 引用

指针常量 常量指针
指针常量int* const p指针方向是常量,不能被修改;
常量(的)指针const int* p指向的值是常量,不能被修改。

引用指针
引用指针(reference to pointer)是指一个引用,它引用了一个指针变量的地址。具体来说,int*& aa = &p; 这行代码定义了一个名为 aa 的引用变量,其类型为 int* &,表示它是一个引用,引用了一个指向 int 类型的指针变量。实例

int main()
{
    /*****************/
    int a = 10;
    int *p = &a;
    const int *&q = p;   // 这表示 q 是一个引用,它引用了一个指向 const int 类型的指针
    // const int **q = &p; //const int** <= int**
    /*****************/
    
    /*****************/
    int a = 10;
    const int *p = &a;
    int *&q = p;
    int **q = &p;
    // int** <= const int**
    /*****************/

    /*****************/
    int a = 10;
    int *const p = &a;
    int *&q = p;
    int **q = &p; // *q =
    /*****************/

    /*****************/
    // 写一句代码,在内存的0x0018ff44处写一个4字节的10
    int *const &p = (int*)0x0018ff44;

    int a = 10;
    int *p = &a;
    const int *&q = p; // typeid(q).name()
    // const int*   int*
    /*****************/
    return 0;
}

8 new、delete和malloc、free

new和delete

new和malloc的区别是什么?
delete和free的区别是什么?

malloc和free,称作C的库函数
new和delete,称作运算符

new不仅可以做内存开辟,还可以做内存初始化操作
malloc开辟内存失败,是通过返回值和nullptr做比较;
而new开辟内存失败,是通过抛出bad_alloc类型的异常来判断的。new、delete

// new有多少种?
int *p1 = new int(20);

int *p2 = new (nothrow) int;  //  (nothrow) 是一个可选的参数,表示在分配内存失败时,不抛出异常,而是返回一个空指针。

const int *p3 = new const int(40);

// 定位new  
int data = 0;
int *p4 = new (&data) int(50);
cout << "data:" << data << endl;

malloc、free

int main()
{
    int *p = (int*)malloc(sizeof(int));
    if (p == nullptr)
    {
        return -1;
    }
    *p = 20;
    free(p);

    int *p1 = new int(20);
    delete p1;

    int *q = (int*)malloc(sizeof(int) * 20);
    if (q == nullptr)
    {
        return -1;
    }
    free(q);

    //int *q1 = new int[20];
    int *q1 = new int[20](); // 20个int sizeof(int) * 20
    delete[]q1;

    return 0;
}

free 函数是C标准库中的函数,用于释放通过 malloc、calloc 或 realloc 函数分配的动态内存。
free 函数只接受一个参数,即指向要释放的内存块的指针。这个参数的作用是告诉函数要释放哪个内存块。

为什么 free 函数不需要传入内存块的大小呢?
这是因为在C标准库的内存管理中,malloc、calloc 和 realloc 函数在分配内存时,会在内存块的开头存储一些额外的信息,其中包括分配的内存块的大小。这样,当调用 free 函数释放内存时,它可以根据指针参数来找到对应的内存块,并利用存储的大小信息来正确地释放内存。
因此,free 函数不需要显式地传递内存块的大小参数,它可以通过指针参数定位内存块并自动获取其大小信息。

需要注意的是,在C++中,更推荐使用 new 和 delete 操作符来进行动态内存分配和释放,而不是直接使用 malloc 和 free。使用 new 和 delete 可以更好地与C++的对象模型和异常处理机制结合,并提供更安全、更方便的内存管理方式。