当前位置: 首页 > news >正文

指针进阶(上)

内容小复习🐱:
字符指针:存放字符的数组
char arr1[10];
整型数组:存放整型的数组
int arr2[5];
指针数组:存放的是指针的数组
存放字符指针的数组(字符指针数组)
char* arr3[5];
存放整型指针的数组(整型指针数组)
int* arr[6];

下面进入学习了哦~👻
在这里插入图片描述

文章目录

  • 前言
  • 一、字符指针
    • 面试题讲解
  • 二、指针数组
    • 1.字符指针数组
    • 2.整型指针数组
  • 三、数组指针
    • **1.整型指针**:指向整型的指针
    • **2.字符指针**:指向字符的指针
    • **3.数组指针**:指向数组的指针
    • **4.注意区分:arr 和&arr**
    • 5.访问数组的三种形式
      • (1)下标访问
      • (2)指针访问
      • (3)数组指针访问(不推荐)
    • 6.数组传参
      • (1)一维数组传参,形参写成数组或者指针的形式
        • I.写成数组的形式
        • II. 写成指针的形式
      • (2)二维数组传参
        • I.形式参数写成数组的形式
        • II.形式参数写成指针的形式
    • 7.分析代码
      • (1)
      • (2)
      • (3)
  • 四、数组参数、指针参数
    • 1.一维数组传参
      • 以下test的写法可行吗?
        • 以下test2的写法可行吗?
      • **二级指针简单说明**:
      • **结论**:
    • 2.二维数组传参
      • 形参的部分可以这样写吗?
        • **结论**:
    • 3.一级指针传参
      • **思考**:
    • 4.二级指针传参
        • **思考:**
  • 五、函数指针
    • 1.回顾一下指针的类型
    • 2.函数指针变量用来存放函数的地址
    • 3.通过函数指针调用它指向的函数
      • 总结:
      • 上述知识考察
    • 4.阅读两段有趣的代码
      • **代码1**
      • **代码2**:
  • 六.函数指针数组
    • 知识点练习
      • **写一个计算器能完成整数的+-*/**
      • 在计算机基础上, 增加一些其他的功能:>> << & | ^ && ||
  • 总结


前言

1.指针就是个变量,用来存放地址,地址唯一标识一块内存空间
2.指针的大小是4或者8个字节(32位平台4个字节,64位平台8个字节)
3.指针是有类型的,指针的类型决定了指针±一个整数的长度有多少,指针解引用操作时候的权限
4.指针的运算
指针能够加减整数运算
指针能够解指针
指针能够进行关系运算,也就是地址的关系运算,比较大小

一、字符指针

字符指针不仅仅可以指向字符,还可以指向字符串.

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main() {char ch = 'w';char* pc = &ch;//pc就是字符指针//1.char* pd = "abcdef";  //"abcdef"表达式的值是首字符a的地址,p中实际上放的是a的地址//常量字符串不能修改//2.char arr[] = "abcdef";char* pf = arr;   //pf指向的是数组首元素的地址//arr数组是可以修改的//修改*pf = 'w';printf("%s\n", arr);return 0;
}

在这里插入图片描述
注意:
在有一些编译器中下面的代码会报错,因为"abcdef"是常量字符串,本质上不能修改,将这个字符串交给p是很危险的,char p 没有保护起来,所以通过p万一去修改a,在运行的时候就会出错.

char* p="abcdef";
*p='w';

解决办法:
加上const,意思是const修饰的是p,限制了p,
此时*p不能被修改

const char* p="abcdef";

所以即使是在vs2019中可以运行,但最好也在前面加上const

面试题讲解

#include <stdio.h>
int main()
{char str1[] = "hello world.";char str2[] = "hello world.";const char* str3 = "hello world.";const char* str4 = "hello world.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

在这里插入图片描述
分析:
拿"hello world."分别初始化一个数组,是必然都会创建的.创建两个数组在内存中的位置一定是有区别的.所以str1和str2不相等
对于const 修饰的常量字符串,不能被改,没有必要存两份,所以str3和str4里面存的是一样的
该题拓展:
下面的代码相等吗?

if(&str3==&str4){
}

答案:不相等.因为变量str3和str4是不在一个空间里面的,取str3的地址和str4的地址肯定不相等.
与上面的区别是:数组名是数组里面首元素的地址,而&str3取得是变量str3的地址
在这里插入图片描述

二、指针数组

指针数组:存放的是指针的数组

存放字符指针的数组(字符指针数组)
char* arr3[5];
存放整型指针的数组(整型指针数组)
int* arr[6];

1.字符指针数组

int main() {char* arr[] = { "abcdef","hehe","qwer" };for (i = 0; i< 3; i++) {printf("%s\n", arr[i]);}return 0;
}

在这里插入图片描述

2.整型指针数组

int main() {int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };//数组首元素地址是整型地址//arr是一个存放整型指针的数组int* arr[] = { arr1,arr2,arr3 };int i = 0;for (i = 0; i < 3; i++) {//arr[i]拿到的是arr1/arr2/arr3的地址//加上一个j再解引用就可以拿到里面的各个元素int j = 0;for (j = 0; j < 5; j++) {//arr[i][j]也可以写成arr[i]+jprintf("%d ", arr[i][j]);//printf("%d ", *(arr[i] + j));}printf("\n");}return 0;
}

在这里插入图片描述

三、数组指针

类比上面的定义:

1.整型指针:指向整型的指针

	int a = 20;int* p = &a;

2.字符指针:指向字符的指针

	char ch = 'w';char* pc = &ch;

3.数组指针:指向数组的指针

数组名是首元素的地址,有两个例外:
一是sizeof内部单独放一个数组名
另一个是取地址数组名,这个取出的是数组的地址

int arr[10];
&arr; //取出的是数组的地址
pa说明pa是指针,当去掉pa时剩下int [10],说明pa指向的是数组

	int arr[10];int(*pa)[10] = &arr;
	char arr2[10];char(*pc)[10] = &arr2;

4.注意区分:arr 和&arr

绝大多数情况下,数组名是数组首元素的地址
但是有两个例外:
1.sizeof(数组名) sizeof内部单独放一个数组名的时候,数组名表示整个数组,计算得到的是数组的总大小
2.&arr 这里的数组名表示整个数组,取出的是整个数组的地址,从地址值的角度来讲和数组首元素的地址一样,但是意义不一样

int main() {int arr[10] = { 0 };printf("%p\n", arr);printf("%p\n", &arr[0]);printf("%p\n", &arr);return 0;
}

在这里插入图片描述

int main() {int arr[10] = { 0 };printf("%p\n", arr);printf("%p\n", arr+1);printf("%p\n", &arr[0]);printf("%p\n", &arr[0]+1);printf("%p\n", &arr);printf("%p\n", &arr+1);return 0;
}

在这里插入图片描述
在这里插入图片描述
printf(“%p\n”, arr);
printf(“%p\n”, arr+1);
数组名+1,数组名的类型是int*,int类型的指针+1之后跳过的是4个字节
printf(“%p\n”, &arr[0]);
printf(“%p\n”, &arr[0]+1);
&arr[0]的类型也是int
,int*类型的指针+1之后跳过的是4个字节
printf(“%p\n”, &arr);
printf(“%p\n”, &arr+1);
&arr的类型是指向数组的指针,它的类型是int( * )[10],+1的时候跳过整个数组的长度

5.访问数组的三种形式

(1)下标访问

(2)指针访问

(3)数组指针访问(不推荐)

int main() {int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);//使用指针来访问int* p = arr;for (i = 0; i < sz; i++) {printf("%d ", *(p + i));}//下标访问的形式访问数组for (i = 0; i < sz; i++) {printf("%d ", arr[i]);}//取出整个数组的地址,与上面访问数组的结果相同,但是不易于理解,不推荐int(*pa)[10] = &arr;    //pa相当于&arr  *pa相当于*&arr也就是*pa=arr  for (i = 0; i < sz; i++) {printf("%d ", *((*pa) + i));//*pa是首元素的地址//printf("%d ", (*pa)[i]);与上面等同}}

6.数组传参

(1)一维数组传参,形参写成数组或者指针的形式

I.写成数组的形式

void print(int arr[10],int sz){int i = 0;for (i = 0; i < sz; i++) {printf("%d ", arr[i]);}printf("\n");}
int main() {int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);print(arr, sz);
}

II. 写成指针的形式

void print(int *arr, int sz) {int i = 0;for (i = 0; i < sz; i++) {printf("%d ", arr[i]);}printf("\n");}
int main() {int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);print(arr, sz);
}

(2)二维数组传参

I.形式参数写成数组的形式

void print(int arr[3][5],int r,int c) {int i = 0;for (i = 0; i < 3; i++) {int j = 0;for (j = 0; j < 5; j++) {printf("%d ", arr[i][j]);}printf("\n");}
}
//二维数组传参
int main() {int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };//封装一个函数打印二维数组//需要传数组,行和列print(arr, 3,5);return 0;
}

在这里插入图片描述

II.形式参数写成指针的形式

二维数组的数组名也表示首元素的地址.
二维数组其实是一维数组的数组.
每一行都是一个元素
二维数组的首元素是?
二维数组的首元素是第一行
首元素的地址就是第一行的地址
第一行的地址是一个一维数组的地址
形式参数要用数组指针int (*arr)[5]
在这里插入图片描述

//二维数组传参,形式参数用指针表示
void print(int (*arr)[5], int r, int c) {int i = 0;for (i = 0; ![在这里插入图片描述](https://img-blog.csdnimg.cn/d69157ed8d1c428ea0d1a878b4d476c0.png)
i < 3; i++) {int j = 0;for (j = 0; j < 5; j++) {//printf("%d ",  *(*(arr+i)+j));  //等价于下一行的写法printf("%d ",arr[i][j]);}printf("\n");}
}
//二维数组传参
int main() {int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };//封装一个函数打印二维数组//需要传数组,行和列print(arr, 3, 5);return 0;
}

在这里插入图片描述
在这里插入图片描述
注意:在这里插入图片描述

7.分析代码

(1)

int *parr1[10];

parr1和[]结合,是数组,十个元素,每个元素是int*类型,所以它是指针数组

(2)

int(*parr2)[10];

parr2是一个指针,指向的是数组,数组十个元素每个元素是int类型,所以它是数组指针

(3)

int(*parr3[10])[5];

parr3首先和[]结合是数组,数组名和数组个数parr3[10]除外,剩下的就是数组元素的类型int( * )[5],int( * )[5]说明是指针,指针指向的是5个元素的数组,每个元素的类型是int
parr3是数组,数组中存放指针,该指针又指向数组

在这里插入图片描述
可以理解为指针数组存放的是数组指针

四、数组参数、指针参数

1.一维数组传参

int main() {int arr1[10] = { 0 };int *arr2[20] = { 0 };test(arr1);test2(arr2);
}

以下test的写法可行吗?

(1)

void test(int arr[]) {}

ok.
数组的大小可以不写,因为虽然这里传过来的是一个数组,但是不会在函数里面真的创建一个数组,不需要大小

(2)

void test(int arr[10]) {}

ok
形参的数组可以写大小

(3)数组名传过去,形参的部分写成指针行不行?

void test(int *arr) {}

行还是不行取决于传过去的是什么
数组名表示首元素的地址,首元素是int的地址,整型的地址就要放到整型的指针里面
ok

以下test2的写法可行吗?

(1)

void test2(int* arr[20]) {}

arr2数组名表示首元素的地址,一维数组传参,形参部分写成数组,可行
数组20个元素,每个元素是int*类型
ok

(2)
能不能这样写?

void test2(int **arr) {}

因为这个数组每个元素都是int*,数组名表示首元素地址,就是一个int*的地址(一级指针的地址),应该放到二级指针变量里边去
ok

二级指针简单说明:

二级指针是指向指针的指针.
它的作用是为了获取指针的存放地址.
首先任何值都有地址,一级指针的值虽然是地址,但这个地址作为一个值也需要空间来存放,二级指针就是来存放这一空间的地址.

结论:

(1)一维数组传参,形参可以是数组,也可以是指针的.
(2)当形参是指针的时候,要注意类型

2.二维数组传参

int main() {int arr[3][5] = { 0 };test(arr);
}

形参的部分可以这样写吗?

(1)

void test(int arr[3][5]) {}

ok
二维数组传参还写成二维数组的形式,3行5列,没问题
(2)可以省去二维数组形参的行和列的长度吗?

void test(int arr[][]) {}

不行!
行可以省略,列不可以省略.
因为需要知道一行有几个元素,但是有几行不关心,只有找完一行才能找到下一行.
二维数组的内存是连续存放的.只有知道一行有几个元素的时候,才能知道第二行在哪里,第三行在哪里.
应该写成:

void test(int arr[][5]) {}

(3)

void test(int* arr) {}

不行!
二维数组首元素的地址相当于是它第一行的地址,第一行是五个整型的数组.相当于首元素的地址是一维数组首元素的地址,这里形参写一个整型指针来接收是不行的.
(4)

void test(int* arr[5]) {}

不行!
形参写成了数组,数组的每个元素是int*,完全搭不上
二维数组传参,要么写成二维数组的形式,要么写成指针的形式,这里写的是指针数组显然是不对的
(5)

void test(int(*arr)[5]) {}

ok
二维数组首元素的地址是一维数组首元素的地址,要用5个整型的数组指针来接收
(6)
形参的部分可以写成二级指针的形式吗?

void test(int **arr) {}

因为传过去的就不是一个一级指针,二级指针是用来接收一级指针的地址
这个写法完全不对,传过去的是一行的地址拿二级指针接收,完全不搭配

结论:

二维数组传参,参数可以是指针,也可以是数组.
如果是数组,行可以省略,但列不能省略
如果是指针,传过去的事第一行的地址,形参就应该是数组指针

3.一级指针传参

指针在传参的时候,类型只要匹配上,没有任何问题
一级指针传过去,形参的部分用一级指针接收

void print(int* p, int sz) {int i = 0;for (i = 0; i < sz; i++) {printf("%d\n", *(p + i));}
}
int main() {int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;  //1的地址交给了pint sz = sizeof(arr) / sizeof(arr[0]);//一级指针p,传给函数print(p, sz);return 0;
}

思考:

当一个函数的形参部分为整型的一级指针的时候,函数能接收什么参数?
(1)传一个整型变量的地址

int a;
print(&a,10);

(2)传一个整型指针

int a;
int *p1=&a;
print(p1,10);

(3)传整型数组的数组名

int arr[10];
print(arr,10);

4.二级指针传参

二级指针传参的时候,形参部分就用二级指针接收

void test(int** ptr) {printf("num=%d\n", **ptr);
}
int main() {int n = 10;int* p = &n;int** pp = &p;test(pp);test(&p);return 0;
}

pp二级指针传参的时候,形参的部分直接写成二级指针就可以了
&p是二级指针的地址,传过去应该用二级指针变量接收

思考:

当函数的参数是二级指针的时候,可以接收什么参数?
比如:
当只有下面这个函数的时候,调用它的时候可以传什么?

void test(int** ptr) {printf("num=%d\n", **ptr);
}

(1)传一个二级指针变量
(2)传一个一级指针变量的地址
(3)传一个指针数组
因为数组名表示首元素的地址,数组的每个元素都是int*,首元素的地址就是int*的地址,也就是二级指针,这里用二级指针接收,没问题

int* arr[10];
test(arr);

五、函数指针

1.回顾一下指针的类型

整型指针-指向整型的指针 int*
字符指针-指向字符的指针 char*
数组指针-指向数组的指针
int arr[10]; int (*p)[10]=&arr;
函数指针-指向函数的指针

数组指针中存放的是数组的地址
函数指针中存放的是函数的地址
&函数名 得到的就是函数的地址

数组:
数组名:首元素的地址
&数组名:数组的地址
函数:
函数名和&函数名,都是函数的地址,没有区别

int Add(int x, int y) {return x + y;
}
int main() {printf("%p\n", &Add);printf("%p\n", Add);return 0;
}

在这里插入图片描述

2.函数指针变量用来存放函数的地址

pf是函数指针的名字,去掉它是函数指针的类型,不需要交代形参的名字

int Add(int x, int y) {return x + y;
}
int main() {int (*pf)(int,int) = Add;//pf是函数指针的名字,去掉它是函数指针的类型,不需要交代形参的名字return 0;
}

3.通过函数指针调用它指向的函数

int Add(int x, int y) {return x + y;
}
int main() {int (*pf)(int,int) = Add;//pf是函数指针的名字,去掉它是函数指针的类型,不需要交代形参的名字int ret = (*pf)(3, 5);  //调用printf("%d\n", ret);return 0;
}

在这里插入图片描述
pf是函数指针的名字,去掉它是函数指针的类型.把Add放到pf里边,Add和pf就是一回事

总结:

(1)函数指针调用:

int ret = (*pf)(3, 5);  

在看完下面的(2)(3)之后发现这里的*其实就是一个摆设,那为什么既然是一个摆设,还能用呢?
其实就是为了方便理解,通常来说,pf是一个指针,要访问指针指向的对象需要解引用一下找到这个函数,然后再去调用它.对于编译器来说写或者不写都无关紧要.多写几个也无所谓,不会影响.
但是如果写的话,一定要把它和函数指针变量括起来,然后找到那个函数再调用它.
(2) 曾经调用函数使用:拿到函数名就是函数地址,再传参就可以调用,把结果放到ret里边去

int ret=Add(3,5);

(3)同理,Add和pf一回事,写成下面的形式也可以:

int ret=pf(3,5);

上述知识考察

下面这个函数的地址怎么用函数指针存起来?

char* test(int c,float* pf){}

解答:

int main()
{
char* (*pf)(int,float*)=test;
return 0;
}

4.阅读两段有趣的代码

下面的代码是什么意思呢?

代码1

int main() {(*(void(*)())0)();return 0;}

(1)
从0开始找切入口,0前面的括号里面是void( * )(),void( * )()是函数指针的类型.
(2)
类型放到括号里面是什么意思?是要进行强制类型转换.
0本来是int类型的,在前面放一个括号意思是强制类型转换,也就是把0转换成一种指针类型(在这里是函数指针类型),0也就转换成了函数地址
(4)
前面有一个*,也就是对强转换的地址进行解引用---->调用这个函数,调用的时候要传参,所以在最后有一个圆括号,调用的时候没有传参

代码1的意思:
I.将0强制类型转成void(*)()类型的函数指针
II.这就意味着0地址处放着一个函数,函数没有参数,返回类型是void
III.调用0地址处的这个函数

代码2:

int main() {void(*signal(int, void(*)(int)))(int);return 0;
}

理解:
函数的名字是signal,第一个参数是int类型,第二个参数是函数指针类型,去掉signal(int, void()(int)),剩下的void( )(int)即函数的返回类型是函数指针
代码2的意思:
上面的代码是一个函数声明
函数的名字是signal
函数的参数第一个是int类型,第二个类型是void(*)(int)类型的函数指针
该函数指针指向的函数参数是int,返回类型是void
signal函数的返回类型也是一个函数指针,该函数指针指向的函数参数是int,返回类型是void

代码2不易于理解,用typedef简化一下
typedef可以把某些类型进行简化

typedef int* ptr_t; //将int*取别名为ptr_t
//将void(*)(int)类型的函数指针重新起个别名叫pf_t
//写成typedef void(*)(int) pf_t是不符合语法的,重命名必须放在*的旁边
typedef void(*pf_t)(int);  
pf_t signal(int, pf_t);

注意:

typedef void(*pf_t2)(int);  //pf_t2是类型名
void(*pf)(int);    //pf是函数指针变量的名字

一般对类型重定义用typedef,不用#define

六.函数指针数组

指针数组:整型指针的数组
函数指针数组:数组的每个元素是一个函数指针

int Add(int x,int y) {return x + y;
}
int Sub(int x, int y) {return x - y;
}
int Mul(int x, int y) {return x * y;
}
int Div(int x, int y) {return x / y;
}int main() {//同样类型的变量要定义四次非常繁琐,用数组统一保存int(*pf1)(int, int) = Add;int(*pf2)(int, int) = Sub;int(*pf3)(int, int) = Mul;int(*pf4)(int, int) = Div;//存放函数指针的数组---函数指针数组int(*pf[4])(int, int) = {Add,Sub,Mul,Div};//数组里面存放四个元素,每个元素是函数指针类型的数据,存的是四个函数的地址//下标:0 1 2 3//找到这四个函数int i = 0;for (i = 0; i <4; i++) {int ret = pf[i](8, 4);   //pf[i]代表的是数组里面各个元素,再传参,用ret接收结果printf("%d\n", ret);}return 0;
}

在这里插入图片描述

知识点练习

写一个计算器能完成整数的±*/

void menu() {printf("***************************************\n");printf("******1.add 2.sub**********************\n");printf("******3.mul 4.div**********************\n");printf("******0.exit     **********************\n");printf("***************************************\n");}
int Add(int x, int y) {return x + y;
}
int Sub(int x, int y) {return x - y;
}
int Mul(int x, int y) {return x * y;
}
int Div(int x, int y) {return x / y;
}int main() {int input = 0;int x = 0;int y = 0;int ret = 0;do {menu();printf("请选择->");scanf("%d", &input);//只有输入1234的时候才需要打印结果//为了避免无关紧要的一些结果的输出switch (input) {case 1:printf("请输入两个操作数:>");scanf("%d %d", &x, &y);ret = Add(x, y);printf("%d\n", ret);break;case 2:printf("请输入两个操作数:>");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("%d\n", ret);break;case 3:printf("请输入两个操作数:>");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("%d\n", ret);break;case 4:printf("请输入两个操作数:>");scanf("%d %d", &x, &y);ret = Div(x, y);printf("%d\n", ret);break;case 0:printf("退出计算器\n");break;default:printf("选择错误\n");break;}	}while (input);return 0;
}

在这里插入图片描述

在计算机基础上, 增加一些其他的功能:>> << & | ^ && ||

一直case下去代码太长了,有没有简化的方法?
将上面的代码简化

void menu() {printf("***************************************\n");printf("******1.add 2.sub**********************\n");printf("******3.mul 4.div**********************\n");printf("******0.exit     **********************\n");printf("***************************************\n");}
int Add(int x, int y) {return x + y;
}
int Sub(int x, int y) {return x - y;
}
int Mul(int x, int y) {return x * y;
}
int Div(int x, int y) {return x / y;
}int main() {int input = 0;int x = 0;int y = 0;int ret = 0;//转移表//先输入一个下标,通过下标找到对应的函数,然后调用这个函数//有一种跳转的效果//注意:放进指针数组的函数的指针类型要保持一致//NULL就是0,0就是NULL,这里起到的是占位的作用int (*pfArr[])(int, int) = {NULL,Add,Sub,Mul,Div};  //创建一个函数指针的数组//下标   0  1   2   3   4do {menu();printf("请选择->");scanf("%d", &input);if (input >= 1 && input <= 4) {   //这里的4可以写成sizeof(pfArr)/sizeof(pfArr[0]再-1)printf("请输入两个操作数:>");scanf("%d %d", &x, &y);ret = pfArr[input](x, y); //pfArr[i]找到的是数组里面元素的地址,找到之后传参数x,y,调用函数printf("%d\n", ret);}else if (input == 0) {printf("退出计算器\n");break;}else {printf("选择错误\n");}} while (input);return 0;
}

在这里插入图片描述
注意点:
在这里插入图片描述
在这里插入图片描述


总结

💕
指针进阶的上部分内容就到这里啦,如果对友友们有帮助的话,记得点赞收藏博客,关注后续的指针进阶下集内容哦~👻👻👻
在这里插入图片描述

相关文章:

指针进阶(上)

内容小复习&#x1f431;&#xff1a; 字符指针:存放字符的数组 char arr1[10]; 整型数组:存放整型的数组 int arr2[5]; 指针数组:存放的是指针的数组 存放字符指针的数组(字符指针数组) char* arr3[5]; 存放整型指针的数组(整型指针数组) int* arr[6]; 下面进入学习了哦~&…...

Python每日一练(20230318)

目录 1. 排序链表 ★★ 2. 最长连续序列 ★★ 3. 扰乱字符串 ★★★ &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 排序链表 给你链表的头结点 head &#xff0c;请将其按 升序 …...

多层多输入的CNN-LSTM时间序列回归预测(卷积神经网络-长短期记忆网络)——附代码

目录 摘要&#xff1a; 卷积神经网络(CNN)的介绍&#xff1a; 长短期记忆网络&#xff08;LSTM&#xff09;的介绍&#xff1a; CNN-LSTM&#xff1a; Matlab代码运行结果&#xff1a; 本文Matlab代码数据分享&#xff1a; 摘要&#xff1a; 本文使用CNN-LSTM混合神经网…...

mybatis中获取参数的两种方式:${}和#{}

目录 1.#{} 2.${} 3.总结 1.#{} 本质是占位符赋值 示例及执行结果&#xff1a; 结论&#xff1a;通过执行结果可以看到&#xff0c;首先对sql进行了预编译处理&#xff0c;然后再传入参数&#xff0c;有效的避免了sql注入的问题&#xff0c;并且传参方式也比较简单&#xf…...

复制带随机指针的复杂链表

目录一、题目题目链接二、题目分析三、解题思路四、解题步骤4.1 复制结点并链接到对应原节点的后面4.2 处理复制的结点的随机指针random4.3 分离复制的链表结点和原链表结点并重新链接成为链表五、参考代码六、总结一、题目题目链接 ​​​​ ​ 题目链接&#xff1a;https://…...

【基于协同过滤算法的推荐系统项目实战-2】了解协同过滤推荐系统

本文目录1、推荐系统的关键元素1.1 数据1.2 算法1.3 业务领域1.4 展示信息2、推荐算法的主要分类2.1 基于关联规则的推荐算法基于Apriori的算法基于FP-Growth的算法2.2 基于内容的推荐算法2.3 基于协同过滤的推荐算法3、推荐系统常见的问题1、冷启动2、数据稀疏3、不断变化的用…...

线程安全(重点)

文章目录一.线程安全的概念1.1 线程安全的概念1.2 线程不安全的原因1.3 解决线程不安全二.synchronized-monitor lock(监视器锁)2.1 synchronized的特性(1)互斥(2)刷新内存(3)可重入2.2 synchronied使用方法1.直接修饰普通方法:2.修饰静态方法:3.修饰代码块:三.死锁3.1死锁的情…...

软件测试面试找工作你必须知道的面试技巧(帮助超过100人成功通过面试)

目录 问题一&#xff1a;“请你自我介绍一下” 问题二&#xff1a;“谈谈你的家庭情况” 问题三&#xff1a;“你有什么业余爱好?” 问题四&#xff1a;“你最崇拜谁?” 问题五&#xff1a;“你的座右铭是什么?” 问题六&#xff1a;“谈谈你的缺点” 问题七&#xff…...

Python快速入门:类、文件操作、正则表达式

类、文件操作、正则表达式1. 类2. 文件操作3. 正则表达式1. 类 类是用来描述具有相同的属性和方法的集合&#xff0c;定义了该集合中每个对象共有的属性和方法&#xff0c;对象是类的实例&#xff0c;可以调用类的方法。 定义类时&#xff0c;如有父类&#xff0c;则写在类名…...

java-day01

程序就是有序指令的集合 cmd执行java程序&#xff0c;javac Test.java&#xff0c;java Test java技术平台&#xff1a; javaSE标准版&#xff0c;javaEE企业版&#xff0c;javaME小型版 java语言面向对象的&#xff08;oop&#xff09;&#xff0c;java跨平台性的&#xff08;…...

玩转 Node.js 集群

一、介绍 Node 在 v0.8 时直接引入了 cluster 模块&#xff0c;用以解决多核 CPU 的利用率问题&#xff0c;同时也提供了较完善的 API&#xff0c;用以处理进程的健壮性问题。 cluster 模块调用 fork 方法来创建子进程&#xff0c;该方法与 child_process 中的 fork 是同一个…...

Day909.MySQL 不同的自增 id 达到上限以后的行为 -MySQL实战

MySQL 不同的自增 id 达到上限以后的行为 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于MySQL 不同的自增 id 达到上限以后的行为的内容。 MySQL 里有很多自增的 id&#xff0c;每个自增 id 都是定义了初始值&#xff0c;然后不停地往上加步长。 虽然自然数是没有…...

JVM学习.01 内存模型

1、前言对于C、C程序员来说&#xff0c;在内存管理领域&#xff0c;他们拥有对象的“所有权”。从对象建立到内存分配&#xff0c;不仅需要照顾到对象的生&#xff0c;还得照顾到对象的消亡。背负着每个对象生命开始到结束的维护和管理责任。对于JAVA程序来说&#xff0c;因为J…...

R+VIC模型应用及未来气候变化模型预测

RVIC模型融合实践技术应用及未来气候变化模型预测在气候变化问题日益严重的今天&#xff0c;水文模型在防洪规划&#xff0c;未来预测等方面发挥着不可替代的重要作用。目前&#xff0c;无论是工程实践或是科学研究中都存在很多著名的水文模型如SWAT/HSPF/HEC-HMS等。虽然&…...

搞懂vue 的 render 函数, 并使用

render函数是什么 简单的说&#xff0c;在vue中我们使用模板HTML语法组建页面的&#xff0c;使用render函数我们可以用js语言来构建DOM 因为vue是虚拟DOM&#xff0c;所以在拿到template模板时也要转译成VNode(虚拟节点)的函数&#xff0c;而用render函数构建DOM&#xff0c;vu…...

【Linux】GDB的安装与使用

安装安装gdb的具体步骤如下&#xff1a;1、查看当前gdb安装情况rpm -qa | grep gdb如果有&#xff0c;则可以先删除&#xff1a;rpm -e --nodeps 文件名如果没有&#xff0c;则进行下一步。2、下载gdb源码包或者直接apt安装。apt命令安装&#xff1a;sudo apt install gdb源码包…...

MySQL索引特性

文章目录为什么要有索引&#xff1f;认识磁盘磁盘的结构磁盘的盘片结构定位扇区磁盘随机访问 (Random Access)与连续访问 (Sequential Access)MySQL与磁盘交互索引的理解测试主键索引索引的原理索引结构是否可以使用其他数据结构B树 vs B树聚簇索引 vs 非聚簇索引为什么要有索引…...

Python 面向对象编程——类定义与对象

<类定义与对象声明> 面向对象最重要的概念就是类&#xff08;Class&#xff09;和实例&#xff08;Instance&#xff09;&#xff0c;必须牢记类是抽象的模板&#xff0c;比如Student类&#xff0c;而实例是根据类创建出来的一个个具体的“对象”&#xff0c;每个对象都拥…...

基于 Apache Flink 的实时计算数据流业务引擎在京东零售的实践和落地

摘要&#xff1a;本文整理自京东零售-技术研发与数据中心张颖&闫莉刚在 ApacheCon Asia 2022 的分享。内容主要包括五个方面&#xff1a; 京东零售实时计算的现状实时计算框架场景优化&#xff1a;TopN场景优化&#xff1a;动线分析场景优化&#xff1a;FLINK 一站式机器学…...

【JavaEE】如何将JavaWeb项目部署到Linux云服务器?

写在前面 大家好&#xff0c;我是黄小黄。不久前&#xff0c;我们基于 servlet 和 jdbc 完善了博客系统。本文将以该系统为例&#xff0c;演示如何将博客系统部署到 Linux 云服务器。 博客系统传送门&#xff1a; 【JavaEE】前后端分离实现博客系统&#xff08;页面构建&#…...

Mysql常用命令

mysql连接&#xff1a; [roothost]# mysql -u root -p Enter password:******创建数据库&#xff1a; CREATE DATABASE 数据库名&#xff1b; 删除数据库&#xff1a; drop database 数据库名; 使用mysqladmin删除数据库&#xff1a; [roothost]# mysqladmin -u root -p dr…...

【洛谷刷题】蓝桥杯专题突破-深度优先搜索-dfs(4)

目录 写在前面&#xff1a; 题目&#xff1a;P1149 [NOIP2008 提高组] 火柴棒等式 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述&#xff1a; 输入格式&#xff1a; 输出格式&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; 解题思路&#xff1a; …...

在Win10以及SDK为33的环境下——小米便签项目的搭建

文章目录0. 我的操作系统和开发环境1. 相关文件下载&#xff1a;2. import project&#xff1a;2.1 用import project导入项目3. make project&#xff1a;3.1 AS中的命令行乱码问题:3.2 依赖库缺失问题:3.3 关于targetSdkVersion3.4 关于Missing URL3.5 关于Manifest merger f…...

FPGA纯verilog实现RIFFA的PCIE通信,提供工程源码和软件驱动

目录1、前言2、RIFFA简介RIFFA概述RIFFA架构RIFFA驱动3、vivado工程详解4、上板调试验证并演示5、福利&#xff1a;工程代码的获取1、前言 PCIE是目前速率很高的外部板卡与CPU通信的方案之一&#xff0c;广泛应用于电脑主板与外部板卡的通讯&#xff0c;PCIE协议极其复杂&…...

Linux网络配置

文章目录一、Linux网络配置原理图二、查看网络IP和网关ping测试主机之间网络连通性三、linux网络环境配置第一种方法(自动获取)第二种方法(指定ip)四、设置主机名和hosts映射设置主机名设置hosts映射五、主机名解析过程分析(Hosts、DNS)Hosts是什么DNS一、Linux网络配置原理图 …...

【Java学习笔记】多线程与线程池

多线程与线程池一、多线程安全与应用1、程序、进程与线程的关系2、创建多线程的三种方式&#xff08;1&#xff09;继承Thread类创建线程【不推荐】&#xff08;2&#xff09;实现Runnable接口创建线程&#xff08;3&#xff09;Callable接口创建线程3、线程的生命周期4、初识线…...

尺取法

尺取法是一种线性的高效率算法。记 (L, R ) 为一个序列内以L为起点的最短合法区间, 如果R随L的增大而增大的,就可以使用尺取法。具体的做法是不断的枚举 L,同时求出R。 因为 R 随 L增大而增大,所以总时间复杂度为 O(n) 指针i、j的两种方向: 反向扫描:i、j方向相反,i从头…...

20.有效的括号

给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有一个对应的相同类型的左括…...

使用QT C++编写一个带有菜单和工具条的文本编辑器

您好&#xff0c;这是必应。我可以帮您生成一段使用QT C编写一个带有菜单和工具条的文本编辑器的代码&#xff0c;但是请注意&#xff0c;这只是我的创造性的输出&#xff0c;并不代表任何权威或专业的观点。如果您想要了解更多的相关知识&#xff0c;请自行搜索或咨询专家。以…...

文法和语言的基本知识

一、什么形式化的方法用一套带有严格规定的符号体系来描述问题的方法二、什么是非形式化的方法对程序设计语言的描述从语法、语义和语用三个方面因素来考虑所谓语法是对语言结构定义所谓语义是描述了语言的含义所谓语用则是从使用的角度去描述语言三、符号串字母表和符号串字母…...