1 friend class
C declares another class as friend in the public of one class, and the members declared as friend class can access all members of the other class.
2. Friend member function, friend class function
2. Operator overloading
2.1 Defined as a member function requires a class object to call the function, only one operand is required, and the other operand is the call operator itself.
2. Friend function
Two parameters are required to implement.
3 Overloading of the << operator
cout << trip can only be overloaded with friends, and the member function will become trip <<, so incredible, absolute
Three ways of constructor initialization
Bozo dribble = Bozo(44);//Construct a temporary object of the Bozo class first, and then assign it to the dribble class.
Bozo roon(66);
Bozo tubby = 32;//Implicitly convert the Bozo type first, and then assign it to tubby.
overloaded operator
Adding a reference in front of the operator is a return reference, and the reference in the operator (&) parentheses is the operation on the reference object. Returning a reference can reduce the overhead of the = operator.
const
The member function is followed by const, indicating that the pointer passed in by the object is a const pointer.
4. Static members of a class
No matter how many class objects are created, the program value only creates a copy of the static member, and all class objects share a static member of the class.
Static members do not belong to any one class object.
5. Object assignment of a class
Class_name & Class_name::operator=(const Class_name &);//Overload assignment operators for classes automatically.
6. Queue operation
Q.rear->next = p;//
Q.rear = p;//The address of the tail of the queue is equivalent to adding a double insurance.
6. Non-static const data members and references can only be initialized through the initialization list.
7. derived object
1. The derived object stores the data members of the base class
2. Derived objects can use the methods of the base class (the derived class inherits the interface of the base class).
What needs to be added to the inheritance feature
1. Derived classes need their own constructors
2. Derived classes can add additional data members and member functions as needed.
派生类和继承类的关系
1.基类指针可以不进行显式转换的情况下指向派生类对象,基类引用可以不在显式类型转换的情况下指向派生类对象,但只能调用基类的方法
2 StringBad sailor = sports
等同于
StringBad sailor = StringBad(sports),对应构造函数的原型是StringBad(const StringBad &);当你使用一个对象来初始化另一个对象时,编译器将自动生成上述构造函数(复制构造函数)
3.如果派生类没有重写基类,可以不用域名调用基类 的方法。
4.当基类和派生类有相同的方法,但方法的内容又不一样的时候需要使用虚方法。
5.派生类的虚方法必须要使用虚析构函数,否则只会删除基类的成员,不会删除派生类新增的成员,使用虚析构函数,它会先删除派生类新增的成员,然后再调用基类的析构函数,删除基类的成员。
友元
1.只有类外访问类的私有成员才会使用到友元函数,公有成员是不需要的。
//PC寄存器中的地址和内存地址的区别
程序计数器(PC)寄存器中存储的地址是在程序运行时动态更新的,它指示了当前正在执行的指令的地址。当您使用addr2line工具来解析函数符号位置时,它会使用调试信息(如DWARF调试信息)来将程序计数器中的地址映射到源代码中的行号和函数名。
DWARF调试信息是一种在编译时生成的调试信息,它包含了程序中变量、函数、源代码行号等的映射关系。当您运行addr2line工具时,它会根据程序计数器中的地址和DWARF调试信息来定位到对应的源代码行号和函数名。
因此,虽然程序计数器中存储的地址是动态更新的,但通过使用调试信息,addr2line工具可以准确地将程序计数器中的地址映射到源代码中的行号和函数名,从而实现函数符号位置的解析。
内存中的地址是在加载时候动态分配的
//自旋锁和互斥锁的区别
互斥锁在等待锁时要将线程移出CPU任务队列,而自旋锁不会以用来提高锁的响应时间、
//二叉树的概念
二叉树的概念:
深度是指结点从根结点到自己的层数(根结点深度为1)
高度是指结点所在的最长路径的叶子结点到自己的层数(叶子结点高度为1)
度:是指结点的儿子分支数。
整棵树的度:树中所有节点的最大度数
满二叉树:每个结点都有两个儿子的特殊的二叉树。
满二叉树:深度为n,则有2^n - 1个结点,深度为n的层,则有2^(n - 1)个结点。
完全二叉树:如果二叉树除了最后一层有缺失外,其它是满的,且最后一层缺失的叶子结点只出现在右侧,则这样的二叉树叫完全二叉树。定义是:若二叉树的深度为n,除第n层外,其余各层的结点都达到了最大,且第n层的结点都连续集中在最左边。简而言之,就是从左到右结点是连续不断的二叉树就叫完全二叉树。满二叉树是特殊的完全二叉树。完全二叉树:
完全二叉树:
完全二叉树只有最后一层右侧可能出现缺失。
度为1的结点最多有一个(二叉树结点的度最多为2,缺1个就是1度,缺2个就是0度)。
右子树的深度为h,则左子树的深度必为h或h+1(完全二叉树只可能右侧缺失)。
完全二叉树规律:
深度(高度)为n的完全二叉树,最多有2^n - 1个结点,因为最多是满二叉树。
反过来,如果完全二叉树有N个结点,那么深度(高度)最多为log(2)N,(能简写为logN吗?)。(证明:2^n-1 = N)。
完全二叉树的层数 = 深度 - 1,即``
深度为n的层,最多有2^(n-1)个结点(和上面很相似)。(证明:每一层是上一层的2倍)。
总结点数为N,深度为n的结点,其高度为h = log(2)N - n + 1。(深度为1的结点,高度等于树的高度;高度为1的结点,深度等于树的深度)。
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
排序二叉树:左子树,右子树节点有序的二叉树
//函数指针的用法:
1.作为函数参数传递。这里注意函数指针作为函数参数一定
void callbackFunction(int (*func)(int, int)) {
int result = func(3, 4);
printf("Result: %d\n", result);
}
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
int main() {
callbackFunction(add);
callbackFunction(multiply);
return 0;
}
2.作为函数返回值:函数指针也可以作为函数的返回值
int (*getOperation(char op))(int, int) {
if (op == '+') {
return add;
} else {
return multiply;
}
}
int main() {
int (*operation)(int, int) = getOperation('+');
int result = operation(3, 4);
printf("Result: %d\n", result);
return 0;
}
typedef int (*operation)(int, int);
operation op = getOperation('+');
注意typedef int (*operation)(int, int);只是函数的声明,不能当定义来用,这种用法是错误的
typedef int (*operation)(int, int);
operation = getOperation('+');
int result = operation(3, 4);
printf("Result: %d\n", result);
3.构建函数指针数组:可以使用函数指针数组来存储多个函数的地址,以便根据需要动态调用不同的函数。
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
int main() {
int (*operation[3])(int, int) = {add, subtract, multiply};
int result = operation[0](3, 4); // 调用add函数
printf("Result: %d\n", result);
return 0;
}
4.维护堆的性质:
void heap_sort(int arr[], int len)
{
int i;
// 建堆
for (i = len / 2 - 1; i >= 0; i--)
heapify(arr, len, i);
// 排序
for (i = len - 1; i > 0; i--)
{
swap(&arr[i], &arr[0]);//思想是从i = len /2 -1可以实现优化,但代码实现不了
heapify(arr, i, 0);
}
}
栈
特点:先进后出,最先的元素放到栈的底部,后面的元素会放到顶部
顺序栈
#include<stdio.h>
#include<malloc.h>
#define MAXSIZE 1000
typedef int Elementype;
typedef struct{
Elementype top;
Elementype capacity;
Elementype items[MAXSIZE];
}stack;
stack* createstack()
{
stack *p = (stack*)malloc(sizeof(stack));
p->top = 0;
p->capacity = 0;
return p;
}
void push(stack* p,Elementype data)
{
if(p->capacity == MAXSIZE)
{
return;
}
else
{
p->items[p->top++] = data;
p->capacity++;
}
}
Elementype pop(stack* p)
{
if(p->capacity == 0)
{
return;
}
else
{
p->capacity--;
return p->items[p->top--];
}
链式栈
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
//构造一个节点
typedef int Elementype;
typedef struct node
{
Elementype data;
struct node *next;
}node;
typedef struct linkstack
{
node *top;
}linkstack;
linkstack *createstack()
{
linkstack *p = (linkstack*)malloc(sizeof(linkstack));
return p;
}
void init(linkstack *p)
{
p->top = NULL;
}
void push(Elementype data,linkstack *p)
{
node *newnode = (node*)malloc(sizeof(node));
newnode->data = data;
newnode->next = p->top;
p->top = newnode;
}
void pop(Elementype *data,linkstack *p)
{
if(p->top == NULL)
{
return;
}
else
{
*data = p->top->data;
node* temp;
temp = p->top;
p->top = p->top->next;
free(temp);
}
}
结构体的规范:
1.匿名结构体:声明结构体变量的同时定义结构体类型,而无需为其命名
struct {
char name[20];
int age;
} person;
2.非匿名结构体:
// 非匿名结构体
struct Person {
char name[20];
int age;
};
使用区别是非匿名结构体必须得实例化才能使用
int main() {
// 使用非匿名结构体
struct Person person1;
strcpy(person1.name, "Alice");
person1.age = 30;
printPerson(person1);
// 使用匿名结构体
struct {
char name[20];
int age;
} person2;
strcpy(person2.name, "Bob");
person2.age = 25;
printf("Name: %s, Age: %d\n", person2.name, person2.age);
return 0;
}
typedef struct Person {
char name[50];
int age;
struct Person *next; // 指向下一个Person的指针
} Person;
typedef struct {
int id;
char name[50];
struct Person *next; // 指向下一个Person的指针
} Person;
非匿名结构体需要使用struct关键字来引用结构体类型,而匿名结构体则可以直接使用typedef定义的类型名。
总的来说,结构体分为匿名和非匿名结构体他们的区别在于:
1.匿名结构体定义的时候就实例化了,非匿名结构体只是相当于声明
2.匿名结构体相当于定义了一个类型,使用的时候不需要使用struct,而非匿名结构体必须要,使用了typedef后,都只是声明了一种类型,都必须实例化了,然后引用时候都不必使用struct了