C语言系列-小知识点积累(一)

1. C/C++ 语言中,宏只是展开,不同于函数调用

#include<stdio.h>
#define POW(a) a * a


int main() {
    int a = 10;
    int b = 2;
    int c = POW(a) / POW(b);
    printf("%d", c);
    return 0;
}

结果为 10 ∗ 10 / 2 ∗ 2 = 100 ≠ 25 10 * 10 / 2 * 2=100 \neq25 1010/22=100=25

2. C/C++语言中的基本类型

笔者之前面试腾讯的时候,被问到C语言中基本类型占用的字节数。对于这个问题,在不同位数的编译器是不一样的,大致如下表所示

类型32位编译器64位编译器
char11
short int22
int44
long int48
float44
double88
long long88
指针48

测试程序如下:

#include<stdio.h>
#include<stdlib.h>

typedef struct A {
    int *a;
    char b;
    float c;
} A;

void main() {
    char a = '1';
    printf("sizeof(char)=%d\n", sizeof(a));
    short int b = 2;
    printf("sizeof(short int)=%d\n", sizeof(b));
    int c = 3;
    printf("sizeof(int)=%d\n", sizeof(c));
    long int d = 4;
    printf("sizeof(long int)=%d\n", sizeof(d));
    long long e = 5;
    printf("sizeof(long long)=%d\n", sizeof(e));
    float f = 6.0;
    printf("sizeof(float)=%d\n", sizeof(f));
    double g = 6.0;
    printf("sizeof(double)=%d\n", sizeof(g));
    char *h = &a;
    long long *l = &e;
    double *m = &g;
    char **n = &h;
    printf("sizeof(char *)=%d, sizeof(long logn *)=%d, sizeof(double *)=%d, sizeof(char **)=%d\n", sizeof(h), sizeof(l), sizeof(m), sizeof(n));
    printf("sizeof(struct A)=%d\n", sizeof(A));

    char str[] = "1234";
    printf("sizeof(str)=%d\n", sizeof(str));
}

windows10 gcc版本(gcc.exe (x86_64-mcf-seh-rev0, Built by MinGW-Builds project) 14.2.0)运行截图如下:
在这里插入图片描述
但是在ubuntu20.04 上,运行结果有点差异
在这里插入图片描述
对于结构体取sizeof,因为结构体会有内存对齐的要求,比如下图的结构体,sizeof的值为16。按照顺序,a占用8个字节,b占用1个字节,c占用4个字节,但是b和c之间会有一段3字节的填充,因为要保证c的开始地址也是自身长度的整数倍,这样避免需要两次内存读取才能读到c。

typedef struct A {
    int *a;
    char b;
    float c;
} A;

还有对于字符串取sizeof,值可能比字面上的字符数多一,因为还有一个隐含的结束字符’\0’。

3. const与define的比较

在C语言中,define定义的宏在预处理阶段直接展开,不做如何的类型安全检查,而const定义的常量在编译运行阶段使用,有类型安全检查,会分配内存,一般放在静态区。

4. 类的构造函数

C++的类的构造函数,没有返回值,函数名就是类名,不可以是虚函数,一般包括默认构造函数、一般构造函数、拷贝构造函数、转换构造函数和移动构造函数。
默认构造函数,如果没有手动实现一般构造函数,编译器将会自动生成一个默认的空的构造函数,如果没有手动实现一个拷贝构造函数,那么编译器将会自动生成一个默认的拷贝构造函数,但默认的拷贝构造函数对于元素都是浅拷贝,如果遇到指针成员,那么原对象和拷贝出来的新对象的这个指针成员会指向同一个块内存。
对于一般构造函数,可以重载,形参不一样。对于拷贝构造函数,传入的参数往往是const Demo &A形式,即一个常引用,引用可以避免函数传参拷贝开销,常引用可以保证拷贝构造函数内部不会修改传入的原对象参数。
装换构造函数,可以允许将一些基本类型转换为类类型,其中就是一个转换构造函数在发挥作用。
最后是移动构造函数,因为在Demo A=Demo()语句中,实际上会调用两个构造函数,一个是默认的或者一般构造函数构造一个匿名对象,然后调用拷贝构造函数,如果有个成员比较重量级,那么拷贝的开销是巨大的,所以就引入了移动构造函数,因为是匿名对象,不会被调用,那么可以直接将匿名对象的元素移动给新对象。
一个测试程序如下:

#include<iostream>
#include<chrono>
#include <cstring>
#define LENGTH 10000000
// g++ constructor.cpp  -fno-elide-constructors -o demo

class Person {
private:
    float weight;
    double salary;
    double *work_times;
public:
    Person(float weight, double salary) {  // 一般构造函数
        this -> weight = weight;
        this -> salary = salary;
        this->work_times = new double[LENGTH];
        std::cout << "general construtor called" << std::endl;
    }

    Person(float weight) {  // 转换构造函数
        this -> weight = weight;
        this->work_times = new double[LENGTH];
        std::cout << "transform constructor called" << std::endl;
    }

    Person(const Person &p) {  // 拷贝构造函数
        this -> weight = p.weight;
        this -> salary = p.salary;
        this->work_times = new double[LENGTH];
        memcpy(this->work_times, p.work_times, sizeof(double) * LENGTH);
        std::cout << "copy constructor called" << std::endl;
    }

    Person(Person &&p) {  // 移动构造函数
        this -> weight = p.weight;
        this -> salary = p.salary;
        this -> work_times = p.work_times;
        p.work_times = NULL;
        std::cout << "move constructor called" << std::endl;
    }
};


int main() {
    Person p1 = 81.9;

    auto start = std::chrono::system_clock::now();

    Person p2 = Person(78.1, 4500);

    auto finish = std::chrono::system_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(finish-start);
    std::cout << double(duration.count())<< "ms" <<std::endl;
    return 0;
}

以下为运行截图,第一次注释掉移动构造函数,第二次去掉注释。
在这里插入图片描述
可以看到,创建变量的时候都是先调用转换构造函数或者一般构造函数构造一个匿名对象,然后有移动构造函数优先使用移动构造函数,因为开销小。此外,运行这种测试程序最好在linux上测试,在windows真的不知道会是什么样子。

5. C++: NULL和nullptr

在C语言里面只有NULL,标识一个值为0的void*指针,但是在C++里面,为了解决函数重载的二义性问题,将NULL定义为了整数0,同时在C++ 11中引入了nullptr标识空指针。

6. C++: 虚函数

首先是虚函数和纯虚函数的比较。虚函数定义为在类的成员函数前面加上一个virtual关键字,可以有实现,纯虚函数定义一般为virtual void work() = 0;,含有纯虚函数的类为抽象类,不能被实例化,且其子类必须实现这个纯虚函数。
虚函数是用来实现多态的,允许父类指针指向子类,然后通过指针调用一个函数,只能到运行期才知道调用的是那个函数,所以构造函数不能是虚函数,因为此时的类型还没有确定。但是析构函数可以是虚函数,一般也建议设置为虚函数,因为析构父类,如果父类的析构函数不是虚函数,那么只会调用父类的析构函数,子类的资源可能不会被正常清理。而如果父类的析构函数是虚函数,那么会先调用子类的析构函数,然后再调用父类的析构函数。如下为相关测试程序:

#include<iostream>

class Life {
private:
    int age;
public:
    virtual void work() = 0;   // 纯虚函数定义
};

class Person: public Life {
private:
    int age;
    double salary;

public:
    virtual void read() {
        std::cout << "Person read" << std::endl;
    }

    virtual void write() {
        std::cout << "Person write" << std::endl;
    }

    virtual ~Person() {
        std::cout << "Person deleted" << std::endl;
    }

    void work() {
        std::cout << "Person work" << std::endl;
    }
};

class Man: public Person {
private:
    int age;
    double salary;

public:
    void read() {
        std::cout << "man read" << std::endl;
    }

    void write() {
        std::cout << "man write" << std::endl;
    }

    ~Man() {
        std::cout << "man deleted" << std::endl;
    }

    void work() {
        std::cout << "man work" << std::endl;
    }
};


int main() {
    Person *A = new Man();
    delete(A);
    return 0;
}

参考

下载方式:https://pan.quark.cn/s/a4b39357ea24 布线问题(分支限界算法)是计算机科学和电子工程领域中个广为人知的议题,它主要探讨如何在印刷电路板上定位两个节点间最短的连接路径。 在这议题中,电路板被构建为个包含 n×m 个方格的矩阵,每个方格能够被界定为可通行或不可通行,其核心任务是定位从初始点到最终点的最短路径。 分支限界算法是处理布线问题的种常用策略。 该算法与回溯法有相似之处,但存在差异,分支限界法仅需获取满足约束条件的个最优路径,并按照广度优先或最小成本优先的原则来探索解空间树。 树 T 被构建为子集树或排树,在探索过程中,每个节点仅被赋予次成为扩展节点的机会,且会次性生成其全部子节点。 针对布线问题的解决,队式分支限界法可以被采用。 从起始位置 a 出发,将其设定为首个扩展节点,并将与该扩展节点相邻且可通行的方格加入至活跃节点队中,将这些方格标记为 1,即从起始方格 a 到这些方格的距离为 1。 随后,从活跃节点队中提取队首节点作为下个扩展节点,并将与当前扩展节点相邻且未标记的方格标记为 2,随后将这些方格存入活跃节点队。 这过程将持续进行,直至算法探测到目标方格 b 或活跃节点队为空。 在实现上述算法时,必须定义个类 Position 来表征电路板上方格的位置,其成员 row 和 col 分别指示方格所在的行和。 在方格位置上,布线能够沿右、下、左、上四个方向展开。 这四个方向的移动分别被记为 0、1、2、3。 下述表格中,offset[i].row 和 offset[i].col(i=0,1,2,3)分别提供了沿这四个方向前进 1 步相对于当前方格的相对位移。 在 Java 编程语言中,可以使用二维数组...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值