c++随笔记录

目录

1.sizeof和strlen的区别

2.三目运算符

3.一维数组名作用

4.二维数组名作用

5.函数的声明

6指针和引用的区别

7.空指针和野指针

8.const关键字

9.指针和数组

10.指针和函数

11.结构体

12.volatile关键字

13.static关键字

14.define与const区别

15.new/delete与malloc/free区别

16.define与typedef区别

二 c++核心编程(封装、继承、多态)

2.1封装-类

1.类和结构体区别

2.继承

3.构造函数

4.构造函数调用规则

5.析构函数

6.深拷贝和浅拷贝

7.静态成员

8.非常关键内容

9.this指针(本质相当于指向类对象的指针常量)

10.友元

2.2继承

2.3多态


一 c++基础

1.sizeof和strlen的区别

strlen是C语言中统计字符串长度的关键字,其统计遇到的第一个\0前面的有效字符(不论显示还是隐式),sizeof统计占据的所有的内存字节数,包含显示\0和隐式\0。

2.三目运算符

表达式1?表达式2:表达式3;

3.一维数组名作用

  • 统计整个数组的字节数;
  • 数组元素的首地址
  • 数组地址

数组首地址与数组地址在值上是相等的,但是两者存在本质的区别,如下面代码中的解释说明:

int arr[3] = {10,20,30}; //1.数组名:统计数组字节大小 cout << sizeof(arr); // 输出12(3个int,每个4字节) cout << sizeof(arr[0]); // 输出4(首元素的大小) //2.数组地址和数组元素的首地址 //arr和&arr两者在值上是相等的,但是本质不同 //arr(首元素地址)的类型是int*(指向int的指针),步长是sizeof(int)=4字节。 //&arr(整个数组地址)的类型是int (*)[3](指向int[3]数组的指针),步长是sizeof(int[3])=12字节 cout << "&arr(整个数组地址):" << &arr << endl; cout << "arr(首元素地址):" << arr << endl; 

4.二维数组名作用

  • 统计整个数组的字节数;
  • 数组名作为 “指向一维数组的指针”(默认隐式转换,这里的维度看行数)。例如int[2][3],它是 “包含 2 个元素的数组”,每个元素又是 “包含 3 个 int 的数组”;arr隐式转换为 “指向其第一个一维子数组的指针”,类型是 int (*)[3]。
  • 整个二位数组的地址:&数组名
  • 一维数组的数组名:数组名+下标
int arr[2][3] = {{1,2,3}, {4,5,6}}; //1.数组名:统计整个二位数组字节大小 cout << sizeof(arr); // 输出24(2个子数组 × 3个int × 4字节/int) //2.数组名:指向一维数组 int (*p)[3] = arr; // 等价于 p = &arr[0](arr[0]是第一个一维子数组) cout << (*p)[0]; // 输出1(访问第一个子数组的第0个元素) cout << (*(p+1))[0]; // 输出4(p+1指向第二个子数组,步长是sizeof(int[3])=12字节) //3.整个二位数组地址:&数组名 int (*p_arr)[2][3] = &arr; //4.数组名 + 下标(如arr[0])—— 是 “一维数组的数组名” int* p_elem = arr[0]; // 等价于 p_elem = &arr[0][0] cout << *p_elem; // 输出1

5.函数的声明

函数的声明可以有多次,但是函数的定义只能有一次。

6指针和引用的区别

一定切记指针本身就是变量,存储某个变量的地址,所以存在指向指针的指针;但是引用表示对某个变量起别名,内存不会再单独开辟一个空间,与引用的变量共享同一块内存空间,引用必须初始化,引用的实质相当于指向指针常量,指针的指向不会改变,但是值可以修改;

7.空指针和野指针

  • 空指针:主要用于对指针进行初始化,空指针指向的内存空间是不允许被访问的(内存编号0 ~255为系统占用内存,不允许用户访问);
  • 野指针:简单理解野指针就是指向无效内存地址的指针变量,其产生原因如下:

        1)指针被释放后未置空;

        2)指针未进行初始化;

        3)指针指向局部变量;

8.const关键字

  • 指针常量:const修饰p;int* const ptr = &value;
  • 常量指针:const修饰*p;const int *p = &a;
  • class A { void func() const; };-->const成员函数内部对于变量和值,对于变量不能修改(只读操作);对于函数只能调用同意类下的const成员函数;const类只能调用它的const成员函数。

9.指针和数组

  • 指针数组:数组里面的每个元素都是指针;int *ptr_arr[5];
  • 数组指针:指针指向数组;int (*arr_ptr)[5];

10.指针和函数

  • 函数指针:指向函数的指针;int (*func_ptr)(int, int);
  • 指针函数:函数的返回值是指针;int *create_array(int size);

11.结构体

  • 结构体变量的定义方式:

        1)struct 结构体名 结构体变量;-->struct teacher t1;

        2)  struct 结构体名 结构体变量 = {成员1的值,成员2的值};

        3)struct 结构体名 {} 结构体变量;

  • 结构体作为函数参数

        传递方式有两种: 值传递/地址传递

特殊情况:结构体作为函数参数时,与const结合;(防止值被修改)

结构体实例化应用:当定义数据协议时,可以将数据定义为一个结构体去定义和解析数据。

结构体默认的访问和继承权限都是public,而类全是private;

结构体对齐面试常考知识点

CPU 访问内存时,并不是逐个字节读取,而是按字长(Word Size) 读取(比如 32 位 CPU 字长是 4 字节,64 位是 8 字节)。如果数据的起始地址是其自身大小的整数倍,CPU 可以一次读取完成,效率最高;否则就需要多次读取并拼接,效率降低。

结构体的总大小:必须是其最大成员类型大小的整数倍(不足则补填充字节)

成员对齐:每个成员的起始地址必须是其自身大小的整数倍(比如 char 占 1 字节,无限制;int 占 4 字节,起始地址需是 4 的倍数)。

写在前面:对于下面两个结构体,虽然结构体中的内容不同,但是由于结构体中的成员变量顺序不同,所以结构体大小也不同。
Unoptimized 中 char 后直接 int,需要填充 3 字节;而 Optimized 中 char 后接 short,仅需填充 1 字节。

我的理解:看第一个成员变量后面紧随的变量类型。

 #include <stdio.h> // 示例1:未优化的成员顺序(有填充字节) struct Unoptimized { char a; // 1字节 int b; // 4字节 short c; // 2字节 }; // 示例2:优化后的成员顺序(减少填充) struct Optimized { char a; // 1字节 short c; // 2字节 int b; // 4字节 }; int main() { printf("Unoptimized size: %zu bytes\n", sizeof(struct Unoptimized)); printf("Optimized size: %zu bytes\n", sizeof(struct Optimized)); return 0; }

代码输出:

Unoptimized size: 12 bytes Optimized size: 8 bytes 内部解析: struct Unoptimized: char a:占用地址 0(1 字节),地址 1-3 填充 3 字节(为了让 int b 的起始地址是 4 的倍数)。 int b:占用地址 4-7(4 字节)。 short c:占用地址 8-9(2 字节),地址 10-11 填充 2 字节(整体大小需是最大成员 4 字节的整数倍)。 总大小:1+3+4+2+2 = 12 字节。 struct Optimized: char a:占用地址 0(1 字节),地址 1 填充 1 字节(让 short c 起始地址是 2 的倍数)。 short c:占用地址 2-3(2 字节)。 int b:占用地址 4-7(4 字节)。 总大小:1+1+2+4 = 8 字节(刚好是最大成员 4 字节的 2 倍)。

12.volatile关键字

  • 防止编译器优化,告诉代码从内存去读取值,而不是通过寄存器的缓存去读取值。
  • 资源共享:中断与主程序/多线程之间共享数据。

13.static关键字

  • 静态变量:必须初始化;类内声明,类外初始化;作用周期贯穿整个程序,但作用范围只限局部范围内;属于类,而不是属于某个具体的对象;
  • 静态函数:无需创建对象,可直接通过类名访问;属于类,而不是属于某个具体的对象;

14.define与const区别

  • define简单理解只是无脑替换,不进行类型检查,define定义的宏常量替换后不会占内存;
  • const会进行类型检查,const定义的常量会占据内存;

15.new/delete与malloc/free区别

  • new/delete 是 C++ 的运算符malloc/free 是 C 语言的函数,二者都用于动态内存管理;
  • new按对象类型自动计算对应内存空间(如new int分配 4 字节);malloc需要指定字节数(如malloc(4));
  • new直接返回对应类型指针(如int*),无需强制类型转换;malloc返回void*,必须手动强制类型转换。

16.define与typedef区别

  • define只是简单的文本替换,不涉及任何语法检查,工作阶段处于编译之前;
  • typedef是关键字,其作用为起别名,单纯从这个角度来讲类似于引用,在编译阶段起作用;

二 c++核心编程(封装、继承、多态)

2.1封装-类

1.类和结构体区别

类默认访问和继承是private,结构体默认访问和继承是public。

2.继承

公有继承、私有继承、保护继承:

公有继承:父类中的公有(public)和保护(protected)属性到子类中还是公有和保护属性;父类中的私有成员也会被子类继承,占用子类对象的内存空间,但子类完全无法直接访问这些私有成员。

保护继承:父类中的公有和保护属性到子类中变为保护属性;父类中的私有成员也会被子类继承,占用子类对象的内存空间,但子类完全无法直接访问这些私有成员。

私有属性:父类中的公有和保护属性到子类中变为私有属性;父类中的私有成员也会被子类继承,占用子类对象的内存空间,但子类完全无法直接访问这些私有成员。

3.构造函数

  • 主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用;
  • 构造函数与类同名;
  • 具体可分为无参构造、有参构造和拷贝构造;

4.构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数:

        1.默认构造函数(无参,函数体为空)        2.默认析构函数(无参,函数体为空)        3.默认拷贝构造函数,对属性进行值拷贝构造函数调用规则如下:如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造;如果用户定义拷贝构造函数,c++不会再提供其他构造函数;

5.析构函数

        主要作用在于对象销毁前系统自动调用,执行一些内存清理工作。构造函数和析构函数如下所示:

#include <iostream> #include <cstring> // 用于字符串操作 using namespace std; // 定义Person类,包含构造、拷贝构造、析构函数 class Person { private: string name; // 姓名 int age; // 年龄 public: // 1. 无参构造函数(默认构造) // 如果不手动定义,编译器会自动生成空的无参构造 Person() { cout << "调用【无参构造函数】" << endl; name = "未知"; age = 0; } // 2. 有参构造函数 // 自定义参数初始化成员变量 Person(string n, int a) { cout << "调用【有参构造函数】" << endl; name = n; age = a; } // 3. 拷贝构造函数 // 用已有对象初始化新对象,参数必须是const引用(const防止修改原对象,引用避免无限递归) Person(const Person& p) { cout << "调用【拷贝构造函数】" << endl; // 把原对象p的成员值复制给新对象 name = p.name; age = p.age; } // 4. 析构函数 // 对象销毁时自动调用,用于释放资源(如堆内存、文件句柄等) // 无参数、无返回值,名称是~类名 ~Person() { cout << "调用【析构函数】,销毁对象:" << name << endl; } // 成员函数:打印对象信息,方便验证 void showInfo() { cout << "姓名:" << name << ",年龄:" << age << endl; } }; // 测试函数:验证拷贝构造的调用场景(值传递参数) void testCopy(Person p) { p.showInfo(); } int main() { // ========== 1. 调用无参构造 ========== cout << "--- 创建无参对象p1 ---" << endl; Person p1; p1.showInfo(); // ========== 2. 调用有参构造 ========== cout << "\n--- 创建有参对象p2 ---" << endl; Person p2("张三", 20); p2.showInfo(); // ========== 3. 调用拷贝构造 ========== cout << "\n--- 用p2拷贝创建p3 ---" << endl; Person p3 = p2; // 场景1:用已有对象初始化新对象 p3.showInfo(); cout << "\n--- 函数参数值传递调用拷贝构造 ---" << endl; testCopy(p2); // 场景2:函数参数为值传递时,拷贝构造新对象 // ========== 4. 析构函数调用 ========== // 程序结束时,栈上的对象会按“先创建后销毁”的顺序调用析构 cout << "\n--- 程序结束,对象开始销毁 ---" << endl; return 0; }

6.深拷贝和浅拷贝

  • 浅拷贝:一定切记是共享内存空间,对于像 intfloatchar 这样的基本数据类型,浅拷贝会创建一个新的、独立的副本;对于像指针、引用这样的复杂类型,浅拷贝只会复制指针本身,而不会复制指针指向的内容。结果就是,新旧两个对象会共享同一块内存资源。
  • 深拷贝:不共享内存空间,对于基本数据类型,和浅拷贝一样,创建新副本;对于指针、引用等复杂类型,深拷贝会不仅复制指针,还会为指针指向的内容分配新的内存,并将内容也复制过去。最终,新旧对象拥有各自独立的资源,互不干扰
  • 浅拷贝带来的问题:内存的重复释放问题,解决办法,采用深拷贝。

7.静态成员

静态成员变量:

  • 类内声明,类外初始化;
  • 它属于整个类,而不属于某个具体的对象;
  • 作用范围有限,但生命周期贯穿整个程序;
  • 相当于局部全局变量;

静态成员函数:

  • 可通过命名空间直接访问,不需要通过创建对象去访问;
  • 它属于整个类,而不属于某个具体的对象;
  • 只能访问静态成员,不能访问非静态成员;

8.非常关键内容

在c++中成员变量和成员函数是分开存储,只有非静态成员变量才会占据类对象的内存空间,静态成员变量和函数与常规成员函数不会占用类对象的内存空间。

9.this指针(本质相当于指向类对象的指针常量)

写在前面:常见用法解决命名冲突、返回对象本身实现链式调用,本质是让成员函数 “知道操作哪个对象”。

  • 定义:this指针是 C++ 编译器自动为每个非静态成员函数添加的一个隐藏的、常量指针const指针),它指向调用该成员函数的那个对象本身。简单说:谁调用函数,this就指向谁。
  • this指针的用途:

        当形参和成员变量同名时,可用this指针来区分,如代码1;        在类的非静态成员函数中返回对象本身,可使用return *this,如代码2;

        在成员函数中访问当前对象的地址 / 本身,如代码3;

  • this的类型是类名* const:指针本身不能被修改(不能让this指向其他对象),但指向的对象内容可以改;(指针常量)
  • 只有非静态成员函数才有this指针(静态成员函数属于类,不依赖对象,因此没有this)。
  • 如果空指针调用 “不访问成员变量” 的成员函数,不会崩溃(因为函数代码在代码区,和对象无关);如果调用 “访问成员变量” 的成员函数,会崩溃(因为this是 NULL,this->age等价于NULL->age,非法访问内存):

代码1:

#include <iostream> using namespace std; class Person { public: int age; void setAge(int age) { // this->age :指向当前对象的成员变量age // age :函数的形参age this->age = age; } void showAge() { // 即使没有命名冲突,this也在默默工作 // 等价于 cout << this->age << endl; cout << "年龄:" << age << endl; } }; int main() { Person p1, p2; // 调用p1的setAge,this指向p1 p1.setAge(18); // 调用p2的setAge,this指向p2 p2.setAge(20); p1.showAge(); // 输出:18(this指向p1) p2.showAge(); // 输出:20(this指向p2) return 0; }

        代码2:

#include <iostream> using namespace std; class Person { public: int age; // 返回对象本身的引用,支持链式调用 Person& addAge(int num) { this->age += num; return *this; // *this 就是当前对象本身 } }; int main() { Person p; p.age = 10; // 链式调用:连续调用addAge p.addAge(5).addAge(3); cout << p.age << endl; // 输出:18(10+5+3) return 0; }

代码3:

void Person::showAddr() { cout << "当前对象的地址:" << this << endl; // 输出对象的内存地址 cout << "当前对象的年龄:" << (*this).age << endl; // *this 等价于对象本身 }

代码4:

    class Test { public: void func1() { cout << "无成员变量访问" << endl; } void func2() { cout << this->age << endl; } // 访问成员变量 int age; }; int main() { Test* p = NULL; p->func1(); // 不会崩溃(输出:无成员变量访问) // p->func2(); // 崩溃!NULL指针访问成员变量 return 0; }

    10.友元

    总结:只要看见friend关键字,那么这个函数就可以访问引入它的类的私有成员。

    2.2继承

    1.继承方式

    public、protect、private(上面已经介绍过)

    2.继承中的构造和析构

    子类继承父类时,继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反。

    3.菱形继承

    何为菱形继承,可以简单理解为,两个父类继承同一个爷爷,孙子类又继承两个父类,继承父类两份相同的数据,会导致资源浪费以及毫无意义,利用虚继承可以解决菱形继承问题,当两个派生类继承同一个基类时,采用虚继承。

    2.3多态

    1.分类

    静态多态:

    定义:编译器在编译阶段就确定要调用哪个函数,也叫 “早绑定”。

    实现方式:函数重载、运算符重载,代码1;

    动态多态:

    定义:编译器在编译阶段无法确定调用哪个函数,直到程序运行时才确定,也叫 “晚绑定”。

    实现方式:虚函数(virtual)+ 继承 + 父类指针 / 引用指向子类对象,代码2;

    代码1:

    #include <iostream> using namespace std; // 函数重载:同一个函数名,参数列表不同(静态多态) class Calculator { public: // 计算两个int的和 int add(int a, int b) { return a + b; } // 计算三个int的和(参数个数不同) int add(int a, int b, int c) { return a + b + c; } // 计算两个double的和(参数类型不同) double add(double a, double b) { return a + b; } }; int main() { Calculator calc; // 编译时就确定调用哪个add函数 cout << calc.add(1, 2) << endl; // 调用2个int参数的add,输出3 cout << calc.add(1, 2, 3) << endl; // 调用3个int参数的add,输出6 cout << calc.add(1.5, 2.5) << endl; // 调用2个double参数的add,输出4.0 return 0; } 

    代码2:

    #include <iostream> using namespace std; // 父类:动物 class Animal { public: // 虚函数:动物叫(核心,开启动态多态) virtual void makeSound() { cout << "未知动物的叫声" << endl; } // 析构函数也建议设为虚函数(避免子类析构不执行) virtual ~Animal() {} }; // 子类:猫 class Cat : public Animal { public: // 重写父类的虚函数(override可选,建议加,编译器会检查重写是否正确) void makeSound() override { cout << "喵喵喵" << endl; } }; // 子类:狗 class Dog : public Animal { public: void makeSound() override { cout << "汪汪汪" << endl; } }; // 统一的接口函数(接收父类引用) void animalCry(Animal &animal) { animal.makeSound(); // 运行时确定调用哪个子类的函数 } int main() { Cat cat; Dog dog; // 父类引用指向子类对象,调用不同的makeSound animalCry(cat); // 输出:喵喵喵 animalCry(dog); // 输出:汪汪汪 // 父类指针指向子类对象(另一种方式) Animal *p1 = &cat; p1->makeSound(); // 输出:喵喵喵 Animal *p2 = &dog; p2->makeSound(); // 输出:汪汪汪 return 0; } 

    2.虚函数和纯虚函数

    虚函数:普通虚函数是用 virtual 关键字修饰的、有函数体的成员函数,目的是允许子类重写(override),从而实现动态多态

    纯虚函数:纯虚函数是用 virtual 修饰、没有函数体,且以 = 0 结尾的成员函数,目的是强制子类必须重写该函数,同时将包含它的类变为抽象类,抽象类不能实例化对象(无法创建 Animal a; 这样的对象)。

    三.STL

    STL(Standard Template Library,标准模板库);从广义上分为容器(container) 算法(algorithm) 迭代器(iterator),容器和算法之间通过迭代器进行无缝连接。

    STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器

    1. 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。
    2. 算法:各种常用的算法,如sort、find、copy、for_each等
    3. 迭代器:扮演了容器与算法之间的胶合剂。
    4. 仿函数:行为类似函数,可作为算法的某种策略。
    5. 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
    6. 空间配置器:负责空间的配置与管理。

    Read more

    【数据结构与算法】环与相遇:链表带环问题的底层逻辑与工程实现

    【数据结构与算法】环与相遇:链表带环问题的底层逻辑与工程实现

    🔥小龙报:个人主页 🎬作者简介:C++研发,嵌入式,机器人等方向学习者 ❄️个人专栏:《C语言》《【初阶】数据结构与算法》 ✨ 永远相信美好的事情即将发生 文章目录 * 前言 * 一、带环链表 * 1.1题目 * 1.2 算法原理 * 1.3 代码 * 1.4 数学证明 * 1.4.1 为什么带环slow与fast必定能相遇? * 1.4.2 fast一定只能走2步吗?可以是2步甚至更多吗? * 1.4.2.1 以3步为例 * 1.4.3结论 * 二、环形链表(寻找相遇点) * 2.1 题目

    By Ne0inhk

    Python金融数据获取终极指南:告别繁琐,5分钟掌握专业级数据源

    Python金融数据获取终极指南:告别繁琐,5分钟掌握专业级数据源 【免费下载链接】mootdx通达信数据读取的一个简便使用封装 项目地址: https://gitcode.com/GitHub_Trending/mo/mootdx 还在为金融数据获取而苦恼吗?面对复杂的行情接口、繁琐的数据格式转换,很多数据分析师和量化交易爱好者都感到力不从心。今天,我将为你揭秘一个强大的Python工具——mootdx,它能让你轻松获取通达信金融数据,为你的投资分析提供坚实的数据支撑。 🎯 你的数据获取痛点,我们懂! 数据获取的三大难题: * 接口复杂难上手:传统行情接口学习成本高,文档晦涩 * 数据格式不统一:不同来源的数据格式各异,转换工作繁琐 * 更新维护成本高:数据源不稳定,需要频繁调整和维护 mootdx正是为解决这些痛点而生,它提供了: * 📊 一站式数据解决方案:从历史数据到实时行情,全面覆盖 * 🔄 智能连接优化:自动选择最佳服务器,确保数据稳定获取 * 💡 开发体验升级:简洁的API设计,让数据获取变得简单高效 🚀 快速上手:5分钟开启数据

    By Ne0inhk
    Python Anaconda 换源: 设置清华源

    Python Anaconda 换源: 设置清华源

    为 Anaconda 设置清华源可以极大地提升软件包下载和更新的速度。以下是详细的步骤,分为两个主要部分:为 conda 本身设置频道镜像和为 pip 设置索引镜像。 方法一:通过命令行快速设置(推荐) 这是最快捷的方法,通过执行几条命令即可完成。 1. 打开终端(Windows 用 Anaconda Prompt, Mac/Linux 用 Terminal)。 验证配置: 执行以下命令查看当前的配置,确认 channels 里已经都是清华源的地址。 conda config --show channels (可选但推荐)移除默认的官方频道: 为了避免 conda 在官方源和清华源之间来回切换,可以移除默认的 defaults 频道。 conda config --remove channels defaults 设置搜索时显示频道地址: conda config

    By Ne0inhk

    Python 进阶爬虫:解析知识星球 API

    一、知识星球 API 核心原理与接口分析 知识星球的前端页面采用动态加载技术(JavaScript 渲染),所有内容数据均通过后端 API 接口以 JSON 格式返回,前端再将数据渲染为可视化页面。因此,API 爬虫的核心逻辑是模拟前端请求,直接调用 API 接口获取原始 JSON 数据,而非解析 HTML 页面。 1.1 API 接口基础架构 知识星球的 API 接口遵循 RESTful 设计规范,核心请求域名为<font>https://api.zsxq.com</font>,所有接口均通过 HTTPS 协议传输,确保数据安全性。接口主要分为三大类: * 认证类接口:

    By Ne0inhk