灵魂指针,教给(一)

2024-03-07 12:04
文章标签 指针 灵魂 教给

本文主要是介绍灵魂指针,教给(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

欢迎来到白刘的领域   Miracle_86.-CSDN博客

系列专栏  C语言知识

先赞后看,已成习惯

   创作不易,多多支持!

一、内存和地址

1.1 内存

在介绍知识之前,先来想一个生活中的小栗子:

假如把你放在一个有100间屋子的酒店里,但是没有门牌号,你想让你的好朋友来找你玩,这种情况好朋友就得一个房间一个房间去找,非常的麻烦。但是如果有了门牌号,我们就可以告诉好朋友,他就可以迅速精准地找到你所在的房间。

 把上述的栗子,映射到计算机上,就成为了我们今天所介绍的内存。

我们知道,在计算上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也是会放回内存中。我们在买电脑的时候,内存会有8GB/16GB/32GB等等,这些内存是如何高效管理的呢?

事实上,也是通过把内存划分为一个个的小单元格,每个单元格的大小取1个字节。

bit - ⽐特位
byte - 字节
KB
MB
GB
TB
PB
1byte = 8bit
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB
其中,每个内存单元,相当于⼀个学生宿舍,⼀个⼈字节空间⾥⾯能放8个比特位,就好⽐同学们住的⼋⼈间,每个⼈是⼀个⽐特位。
每个内存单元也都有⼀个编号(这个编号就相当 于宿舍房间的门牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。

生活中,我们把门牌号叫作地址,计算机中也不例外,我们刚说了内存就像宿舍,就像屋子,那它的编号(门牌号)就是它的地址。而在C语言里,我们给它取了个新的名字——指针。

所以我们可以理解为:

内存单元的编号 == 地址 == 指针

1.2 如何理解编址

CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,⽽因为内存中字节很多,所以需要给内存进⾏编址(就如同宿舍很多,需要给宿舍编号⼀样) 。 

计算机中的编址,并不是把每个字节的地址记录下来,⽽是通过硬件设计完成的。 

钢琴、吉他上⾯没有写上“都瑞咪发嗦啦”这样的信息,但演奏者照样能够准确找到每⼀个琴弦的每⼀个位置,这是为何?因为制造商已经在乐器硬件层⾯上设计好了,并且所有的演奏者都知道。本质是⼀种约定出来的共识! 

硬件编址亦是如此。 

⾸先,必须理解,计算机内是有很多的硬件单元,⽽硬件单元是要互相协同⼯作的。所谓的协同,⾄少相互之间要能够进⾏数据传递。但是硬件与硬件之间是互相独⽴的,那么如何通信呢?答案很简单,⽤"线"连起来。⽽CPU和内存之间也是有⼤量的数据交互的,所以,两者必须也⽤线连起来。不过,我们今天关⼼⼀组线,叫做地址总线 

我们可以简单理解,32位机器有32根地址总线,每根线只有两态,表⽰0,1【电脉冲有⽆】,那么
⼀根线,就能表⽰2种含义,2根线就能表⽰4种含义,依次类推。32根地址线,就能表⽰2^32种含
义,每⼀种含义都代表⼀个地址。地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传⼊CPU内寄存器。

二、指针变量和地址

2.1 取地址操作符(&)

我们在操作符那里挖了个坑,详见:

武器大师——操作符详解(下)-CSDN博客

理解了内存与指针的关系,我们再回到C语言,在C语言中创建变量的过程其实本质就是向内存申请空间,eg:

#include <stdio.h>
int main()
{int a = 10;return 0;
}

上述代码,我们创建了整型变量a,我们知道整型是4个字节,所以向内存申请了这四个空间:

0x006FFD70
0x006FFD71
0x006FFD72
0x006FFD73

我们如何得到a的地址呢?这里用到了取地址操作符(&),长得像我们之前学过的按位与。

#include <stdio.h>
int main()
{int a = 10;&a;//取出a的地址printf("%p\n", &a);return 0;
}

虽然我们整型占用四个字节,但是实际上我们只需要知道第一个字节的地址,就可以顺藤摸瓜获取接下来的地址。

2.2 指针变量与解引用操作符(*)

2.2.1 指针变量

那我们通过取地址操作符(&)拿到的地址是⼀个数值,⽐如:0x006FFD70,这个数值有时候也是需要存储起来,⽅便后期再使⽤的,那我们把这样的地址值存放在哪⾥呢?答案是:指针变量中。eg:

#include <stdio.h>
int main()
{int a = 10;int* pa = &a;//取出a的地址并存储到指针变量pa中return 0;
}

指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。

2.2.2 如何拆解指针类型

我们看到pa的类型是 int* ,我们该如何理解指针的类型呢?  

int a = 10;
int * pa = &a;
这⾥pa左边写的是 int* * 是在说明pa是指针变量,⽽前⾯的 int 是在说明pa指向的是整型(int)
类型的对象。
2.2.3 解引用操作符( * )

我们将地址保存起来,未来是要使⽤的,那怎么使⽤呢? 

在现实⽣活中,我们使⽤地址要找到⼀个房间,在房间⾥可以拿去或者存放物品。 

C语⾔中其实也是⼀样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这⾥必须学习⼀个操作符叫解引⽤操作符(*)。

#include <stdio.h>
int main()
{int a = 100;int* pa = &a;*pa = 0;return 0;
}

 上述代码第7行就使用了解引用,*pa其实就是表示的指针指向的对象,也就是变量a,这步操作相当于把a的值改成了0。有人会问道:“那我直接改a=0不是更简单嘛,为什么非要写一个指针呢?”,其实这样对a多了一种途径,会让你的代码更有灵活性和可操作性。

2.3 指针变量的大小

这里先说结论:32位处理器(x86)下是4个字节,64位处理器(x64)下是8个字节。

前⾯的内容我们了解到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储。  

如果指针变量是⽤来存放地址的,那么指针变的⼤⼩就得是4个字节的空间才可以。

同理64位机器,假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要8个字节的空间,指针变量的⼤⼩就是8个字节。 

#include <stdio.h>
//指针变量的⼤⼩取决于地址的⼤⼩
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{printf("%zd\n", sizeof(char *));printf("%zd\n", sizeof(short *));printf("%zd\n", sizeof(int *));printf("%zd\n", sizeof(double *));return 0;
}

x86环境下结果:

x64环境下结果:

注意指针变量的大小跟类型无关!只要是指针类型的变量,在相同的平台下,大小是相同的。

三、指针变量类型的意义

既然我们前面说了,指针变量的大小在相同的平台下是相同的,那不同类型的指针变量有什么意义呢?

3.1 指针的解引用

我们来看下面两段代码:

//代码1
#include <stdio.h>
int main()
{int n = 0x11223344;int *pi = &n; *pi = 0; return 0;
}
//代码2
#include <stdio.h>
int main()
{int n = 0x11223344;char *pc = (char *)&n;*pc = 0;return 0;
}

经过调试我们可以看到,代码1将n的4个字节全改为0,而代码2只改了一个字节。

结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。

3.2 指针+-整数

#include <stdio.h>
int main()
{int n = 10;char *pc = (char*)&n;int *pi = &n;printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc+1);printf("%p\n", pi);printf("%p\n", pi+1);return 0;
}

 我们直接看上述代码以及结果:

我们可以看出,char*类型的指针变量+1的话,跳过了1个字节,而int*类型的跳过了4个字节。这就是指针类型导致的变化。

结论:指针的类型决定了指针向前或向后走的距离有多大

 3.3 void*指针

这是一种特殊类型的指针,void 译为无效、空的,可以理解为无具体类型的指针或者泛型指针,我们在前面学过void类型的函数。它可以接收任意类型的地址,但是我们说人无完人,它也有局限性的,它不可以进行+-以及解引用的操作。

来举个例子:

#include <stdio.h>
int main()
{int a = 10;int* pa = &a;char* pc = &a;return 0;
}

在上⾯的代码中,将⼀个int类型的变量的地址赋值给⼀个char*类型的指针变量。编译器给出了⼀个警告(如下图),是因为类型不兼容。⽽使⽤void*类型就不会有这样的问题。 

 使用void*接收:

#include <stdio.h>
int main()
{int a = 10;void* pa = &a;void* pc = &a;*pa = 10;*pc = 0;return 0;
}

一般void*类型的指针是用在函数参数的部分的,我们以后也会讲到(又挖个坑 *^▽^* )。

四、const修饰指针

4.1 const修饰变量

变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。
但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作⽤。

 

#include <stdio.h>
int main()
{int m = 0;m = 20;//m是可以修改的const int n = 0;n = 20;//n是不能被修改的return 0;
}

上述代码中n是不能被修改的,其实n本质是变量,只不过被const修饰后,在语法上加了限制,只要我们在代码中对n就⾏修改,就不符合语法规则,就报错,致使没法直接修改n。 

但是我们如果绕过n,使用n的地址,去修改n,就可以做到了:

#include <stdio.h>
int main()
{const int n = 0;printf("n = %d\n", n);int*p = &n;*p = 20;printf("n = %d\n", n);return 0;
}

来看运行结果: 

 

我们可以看到这⾥⼀个确实修改了,但是我们还是要思考⼀下,为什么n要被const修饰呢?就是为了不能被修改,如果p拿到n的地址就能修改n,这样就打破了const的限制,这是不合理的,所以应该让p拿到n的地址也不能修改n,那接下来怎么做呢?

4.2 const修饰指针变量

 这里讲个小故事,说有一对小情侣在逛街,女生想吃凉皮,一碗凉皮十块钱,但是男生兜里只有十块钱,女生看了看男生,男生说凉皮一看就不好吃,女生说你要是不想买,我就换男朋友,男生着急了,一狠心说了句,买可以但是你不可以换男朋友,女生点了点头,最后他们幸福的生活在了一起。

这个故事其实就类似于const修饰指针变量。

int *const p;
int const* p;

我们把p看成钱,第一行代码就是,const放在右边,修饰限制指针p所指向的值,这个值不可以更改,就像刚开始,男生不想动自己仅剩的钱。

第二行,const放在左边,就可以看成,男生不想让女生换对象,const修饰限制p指向的对象不可以变。

const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本⾝的内容可变。
const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

五、指针运算

指针运算一共分为三种:指针+-运算、指针-指针、指针的关系运算。

5.1 指针+-整数

由于数组在内存中的存放是连续的,我们根据这一特性,只要知道第一个元素的地址,就可以顺藤摸瓜得到任意想要的元素,这就是指针的灵活性所在。

#include <stdio.h>
//指针+- 整数
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);for(i=0; i<sz; i++){printf("%d ", *(p+i));//p+i 这⾥就是指针+整数}return 0;
}

这里主要看的是p所指向的对象类型,第6行写道,p指向的是arr[0],类型是int,所以+1会跳过4个字节,找到arr[1]。

5.2 指针-指针

//指针-指针
#include <stdio.h>
int my_strlen(char *s)
{char *p = s;while(*p != '\0' )p++;return p-s;
}
int main()
{printf("%d\n", my_strlen("abc"));return 0;
}

这段代码是什么意思呢,这段代码是模拟的strlen函数,首先传进来字符串的首地址s,然后char了个*p指向s所指的地址,也就是p和s都指向一个地方,之后,p开始动了,当p指向的是'\0'的时候停止,用p-s就是p动了几下,也就是两个指针之间有多少个元素。

5.3 指针的关系运算

//指针的关系运算
#include <stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);while(p<arr+sz) //指针的⼤⼩⽐较{printf("%d ", *p);p++;}return 0;
}

这段代码就诠释了指针的大小比较。p<arr+sz(这里补充一点,数组名其实也是个指针,我们后面也会讲这一点的,这里先铺垫一下),p指向的arr的首元素,arr+sz是指向的数组末端(不是最后一个元素,而是最后一个元素的后面)。p++会慢慢让p接近这个地址,也就构成了数组遍历。

六、野指针

什么叫野指针呢,你可以理解为,很野的指针,我们说一个人比较野,因为他很放浪不羁、不受拘束。野指针也是如此,它是瞎乱指的,随机的,没有限制的。

6.1 野指针成因

1.指针初始化
#include <stdio.h>
int main()
{ int *p;//局部变量指针未初始化,默认为随机值*p = 20;return 0;
}
2.指针越界访问
#include <stdio.h>
int main()
{int arr[10] = {0};int *p = &arr[0];int i = 0;for(i=0; i<=11; i++){//当指针指向的范围超出数组arr的范围时,p就是野指针*(p++) = i;}return 0;
}
3.指针指向的空间被释放
#include <stdio.h>
int* test()
{int n = 100;return &n;
}
int main()
{int*p = test();printf("%d\n", *p);return 0;
}

6.2 指针初始化

为了避免野指针瞎乱指,我们提供了NULL这个宏定义,它的值为0,但是我们不可以访问0的地址。初始化如下:

#include <stdio.h>
int main()
{int num = 10;int*p1 = &num;int*p2 = NULL;return 0;
}
6.2.1小心指针越界
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。
6.2.2指针变量不再使用时,及时置NULL,指针使用之前检查有效性

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问,同时使⽤指针之前可以判断指针是否为NULL。

int main()
{int arr[10] = {1,2,3,4,5,67,7,8,9,10};int *p = &arr[0];for(i=0; i<10; i++){*(p++) = i;}//此时p已经越界了,可以把p置为NULLp = NULL;//下次使⽤的时候,判断p不为NULL的时候再使⽤//...p = &arr[0];//重新让p获得地址if(p != NULL) //判断{//...}return 0;
}
 6.2.3避免返回局部变量的地址

如造成野指针的第三个例子。

七、assert断言

assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报
错终⽌运⾏。这个宏常常被称为“断⾔”。
assert(p != NULL);

上⾯代码在程序运⾏到这⼀⾏语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。

assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产⽣
任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误
stderr 中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。
assert() 的使⽤对程序员是⾮常友好的,使⽤ assert() 有⼏个好处:它不仅能⾃动标识⽂件和
出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问
题,不需要再做断⾔,就在 #include <assert.h> 语句的前⾯,定义⼀个宏 NDEBUG
#define NDEBUG
#include <assert.h>
然后,重新编译程序,编译器就会禁⽤⽂件中所有的 assert() 语句。如果程序⼜出现问题,可以移
除这条 #define NDBUG 指令(或者把它注释掉),再次编译,这样就重新启⽤了 assert()
句。
assert() 的缺点是,因为引⼊了额外的检查,增加了程序的运⾏时间。
⼀般我们可以在 Debug 中使⽤,在 Release 版本中选择禁⽤ assert 就⾏,在 VS 这样的集成开
发环境中,在 Release 版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题,
Release 版本不影响⽤⼾使⽤时程序的效率。

八、传值调用和传址调用

8.1 strlen的模拟实现

库函数strlen的功能是求字符串⻓度,统计的是字符串中 \0 之前的字符的个数。
函数原型如下:
size_t strlen ( const char * str );
参数str接收⼀个字符串的起始地址,然后开始统计字符串中 \0 之前的字符个数,最终返回⻓度。
如果要模拟实现只要从起始地址开始向后逐个字符的遍历,只要不是 \0 字符,计数器就+1,这样直到 \0 就停⽌。
代码如下:
int my_strlen(const char * str)
{int count = 0;assert(str);while(*str){count++;str++;}return count;
}
int main()
{int len = my_strlen("abcdef");printf("%d\n", len);return 0;
}

8.2 传值调用和传址调用

学习指针的⽬的是使⽤指针解决问题,那什么问题,⾮指针不可呢?
例如:写⼀个函数,交换两个整型变量的值

 我们可能会写出这段代码:

#include <stdio.h>
void Swap1(int x, int y)
{int tmp = x;x = y;y = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前:a=%d b=%d\n", a, b);Swap1(a, b);printf("交换后:a=%d b=%d\n", a, b);return 0;
}

但是来看运行结果:

这是为什么呢?我们来调试一下。

我们发现在main函数内部,创建了a和b,a的地址是0x00cffdd0,b的地址是0x00cffdc4,在调⽤
Swap1函数时,将a和b传递给了Swap1函数,在Swap1函数内部创建了形参x和y接收a和b的值,但是x的地址是0x00cffcec,y的地址是0x00cffcf0,x和y确实接收到了a和b的值,不过x的地址和a的地址不⼀样,y的地址和b的地址不⼀样,相当于x和y是独⽴的空间,那么在Swap1函数内部交换x和y的值,⾃然不会影响a和b,当Swap1函数调⽤结束后回到main函数,a和b的没法交换。Swap1函数在使⽤的时候,是把变量本⾝直接传递给了函数,这种调⽤函数的⽅式我们之前在函数的时候就知道了,这种叫传值调用
结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实 参。

那我们怎么解决这个问题呢?

我们现在要解决的就是当调⽤Swap函数的时候,Swap函数内部操作的就是main函数中的a和b,直接将a和b的值交换了。那么就可以使⽤指针了,在main函数中将a和b的地址传递给Swap函数,Swap函数⾥边通过地址间接的操作main函数中的a和b,并达到交换的效果就好了。

 

#include <stdio.h>void Swap2(int*px, int*py)
{int tmp = 0;tmp = *px;*px = *py;*py = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前:a=%d b=%d\n", a, b);Swap1(&a, &b);printf("交换后:a=%d b=%d\n", a, b);return 0;
}

来看运行结果: 

 

我们可以看到实现成Swap2的⽅式,顺利完成了任务,这⾥调⽤Swap2函数的时候是将变量的地址传递给了函数,这种函数调⽤⽅式叫:传址调用
传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改主调函数中的变量的值,就需要传址调⽤。

这篇关于灵魂指针,教给(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/783445

相关文章

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

【C++学习笔记 20】C++中的智能指针

智能指针的功能 在上一篇笔记提到了在栈和堆上创建变量的区别,使用new关键字创建变量时,需要搭配delete关键字销毁变量。而智能指针的作用就是调用new分配内存时,不必自己去调用delete,甚至不用调用new。 智能指针实际上就是对原始指针的包装。 unique_ptr 最简单的智能指针,是一种作用域指针,意思是当指针超出该作用域时,会自动调用delete。它名为unique的原因是这个

C语言指针入门 《C语言非常道》

C语言指针入门 《C语言非常道》 作为一个程序员,我接触 C 语言有十年了。有的朋友让我推荐 C 语言的参考书,我不敢乱推荐,尤其是国内作者写的书,往往七拼八凑,漏洞百出。 但是,李忠老师的《C语言非常道》值得一读。对了,李老师有个官网,网址是: 李忠老师官网 最棒的是,有配套的教学视频,可以试看。 试看点这里 接下来言归正传,讲解指针。以下内容很多都参考了李忠老师的《C语言非

C和指针:字符串

字符串、字符和字节 字符串基础 字符串就是一串零个或多个字符,并且以一个位模式为全0的NUL字节结尾。 字符串长度就是字符串中字符数。 size_t strlen( char const *string ); string为指针常量(const修饰string),指向的string是常量不能修改。size_t是无符号数,定义在stddef.h。 #include <stddef.h>

【C++】作用域指针、智能指针、共享指针、弱指针

十、智能指针、共享指针 从上篇文章 【C++】如何用C++创建对象,理解作用域、堆栈、内存分配-CSDN博客 中我们知道,你的对象是创建在栈上还是在堆上,最大的区别就是对象的作用域不一样。所以在C++中,一旦程序进入另外一个作用域,那其他作用域的对象就自动销毁了。这种机制有好有坏。我们可以利用这个机制,比如可以自动化我们的代码,像智能指针、作用域锁(scoped_lock)等都是利用了这种机制。

MFC中App,Doc,MainFrame,View各指针的互相获取

纸上得来终觉浅,为了熟悉获取方法,我建了个SDI。 首先说明这四个类的执行顺序是App->Doc->Main->View 另外添加CDialog类获得各个指针的方法。 多文档的获取有点小区别,有时间也总结一下。 //  App void CSDIApp::OnApp() {      //  App      //  Doc     CDocument *pD

C和指针:结构体(struct)和联合(union)

结构体和联合 结构体 结构体包含一些数据成员,每个成员可能具有不同的类型。 数组的元素长度相同,可以通过下标访问(转换为指针)。但是结构体的成员可能长度不同,所以不能用下标来访问它们。成员有自己的名字,可以通过名字访问成员。 结构声明 在声明结构时,必须列出它包含的所有成员。 struct tag {member-list} variable-list ; 定义一个结构体变量x(包含

hot100刷题第1-9题,三个专题哈希,双指针,滑动窗口

求满足条件的子数组,一般是前缀和、滑动窗口,经常结合哈希表; 区间操作元素,一般是前缀和、差分数组 数组有序,更大概率会用到二分搜索 目前已经掌握一些基本套路,重零刷起leetcode hot 100, 套路题按套路来,非套路题适当参考gpt解法。 一、梦开始的地方, 两数之和 class Solution:#注意要返回的是数组下标def twoSum(self, nums: Lis

Qt: 详细理解delete与deleteLater (避免访问悬空指针导致程序异常终止)

前言 珍爱生命,远离悬空指针。 正文 delete 立即删除:调用 delete 后,对象会立即被销毁,其内存会立即被释放。调用顺序:对象的析构函数会被立即调用,销毁该对象及其子对象。无事件处理:如果在对象销毁过程中还涉及到信号和槽、事件处理等,直接 delete 可能会导致问题,尤其是在对象正在处理事件时。适用场景:适用于在确定对象已经不再被使用的情况下,并且不涉及异步处理或事件循环中的

C语言进阶版第8课—指针(2)

文章目录 1. 数组名的理解2. 指针访问数组3. 一维数组传参本质4. 冒泡排序5. 二级指针6. 指针数组7. 指针数组模拟二维数组 1. 数组名的理解 sizeof(数组名)— 这里的数组名代表整个数组,计算的也是整个数组的大小&数组名 — 这里的数组名代表是整个数组,取出的是整个数组的地址除了以上两种,其他任何地方使用数组名,数组名都表示首元素的地址 //数组名