开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

golang 打桩,mock 数据怎么玩?

发表于 2021-10-30

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金

工作中,很多公司都要求效能,要求自动化测试

实际落地的过程中发现,要做单元测试,自动化测试,可能当前这个服务会依赖其他服务的数据,接口等等

那么单测或者自动化的过程中,就可能会由于其他服务的原因或者环境因素导致测试失败,或者阻塞测试

这是一个问题,必须得解决,我们可以采用 golang 自带的 mock 工具来完成,可以在一些必要的地方进行数据打桩,mock 数据

gomock 是什么?

是官方提供的 一个 mock 数据的 框架

官方还提供了 mockgen 工具用来帮助 我们 生成测试代码

github 上项目地址是:github.com/golang/mock

官方是这样介绍 gomock 的:

gomock 是一个用于Go 编程语言的 mocking 框架。它与 Go 的内置测试包集成得很好,但也可以在其他环境中使用。

如何使用 gomock?

使用 gomock 也是非常简单的,先 go get 对应的 工具 gomock 和 mockgen

1
2
shell复制代码go get -u github.com/golang/mock/gomock
go get -u github.com/golang/mock/mockgen

可以写一个 demo 来进行实践

目录结构是这样的

1
2
3
4
5
6
7
8
9
shell复制代码gomock_test
├── go.mod
├── go.sum
├── main.go
└── myfunc
├── mock_myfunc.go
├── myfunc.go
├── myuser.go
└── myuser_test.go

  • mock_myfunc.go 是使用 mockgen 工具生成的
  • myfunc.go 主要是用于模拟调用的底层实现
  • myuser.go 主要是去调用 myfunc.go 里面的接口
  • myuser_test.go 是 对应的单测文件

myfunc.go

  • 编写一个 接口,里面有一个 GetInfo() string 方法,模拟获取信息
1
2
3
4
5
go复制代码package myfunc

type MyFunc interface {
GetInfo() string
}

myuser.go

  • 调用 myfunc.go 中的方法,调用接口获取信息
1
2
3
4
5
6
go复制代码package myfunc

func getUser(m MyFunc) string {
user := m.GetInfo()
return user
}

mock 文件的生成

mock_myfunc.go

这个文件不是我们自己写的,是通过 mockgen 工具生成的 ,生成方式如下:

在 myfunc.go 的同级目录下执行如下语句,填入 source 源文件 和 目标文件即可生成新的 mock 文件

1
shell复制代码mockgen -source=myfunc.go -destination=mock_myfunc.go

我们可以看一下 mockgen 的帮助文档,还有其他的参数供我们使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
shell复制代码# mockgen
mockgen has two modes of operation: source and reflect.

Source mode generates mock interfaces from a source file.
It is enabled by using the -source flag. Other flags that
may be useful in this mode are -imports and -aux_files.
Example:
mockgen -source=foo.go [other options]

Reflect mode generates mock interfaces by building a program
that uses reflection to understand interfaces. It is enabled
by passing two non-flag arguments: an import path, and a
comma-separated list of symbols.
Example:
mockgen database/sql/driver Conn,Driver

-aux_files string
(source mode) Comma-separated pkg=path pairs of auxiliary Go source files.
-build_flags string
(reflect mode) Additional flags for go build.
-copyright_file string
Copyright file used to add copyright header
-debug_parser
Print out parser results only.
-destination string
Output file; defaults to stdout.
-exec_only string
(reflect mode) If set, execute this reflection program.
-imports string
(source mode) Comma-separated name=path pairs of explicit imports to use.
-mock_names string
Comma-separated interfaceName=mockName pairs of explicit mock names to use. Mock names default to 'Mock'+ interfaceName suffix.
-package string
Package of the generated code; defaults to the package of the input with a 'mock_' prefix.
-prog_only
(reflect mode) Only generate the reflection program; write it to stdout and exit.
-self_package string
The full package import path for the generated code. The purpose of this flag is to prevent import cycles in the generated code by trying to include its own package. This can happen if the mock's package is set to one of its inputs (usually the main one) and the output is stdio so mockgen cannot detect the final output package. Setting this flag will then tell mockgen which import to exclude.
-source string
(source mode) Input Go source file; enables source mode.
-version
Print version.
-write_package_comment
Writes package documentation comment (godoc) if true. (default true)
2021/10/30 16:43:25 Expected exactly two arguments

一般用的比较多的就是

  • -source 源文件
  • -destination 目标文件
  • -imports 依赖的需要 import 的包
  • -build_flags 传递给build工具的参数
  • -aux_files 接口文件不止一个文件时附加文件
  • -package 设置 mock 文件的包名,不设置的话,mock 文件的包名默认是 mock_输入文件的包名

通过上述指令生成的 mock 文件如下:

  • NewMockMyFunc

创建一个新的 mock 实例

  • EXPECT

允许调用者指示预期用途的对象

  • GetInfo

mock 的基础方法,也就是我们需要 mock 的方法

具体的如何使用

myuser_test.go

  • myuser.go 对应的单测文件 , 使用了 mock 的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
go复制代码package myfunc

import (
"fmt"
"testing"
gomock "github.com/golang/mock/gomock"
)

func Test_getUser(t *testing.T) {
mockCtl := gomock.NewController(t)
mockMyFunc := NewMockMyFunc(mockCtl)
mockMyFunc.EXPECT().GetInfo().Return("xiaomotong")
v := getUser(mockMyFunc)
if v == "xiaomotong" {
fmt.Println("get user right!")
} else {
t.Error("get error user")
}
}

看到上述单测文件,可以还不是特别明白区别,我们来看看不用 mock 的时候,我们会是如何去写单测呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
go复制代码package myfunc

import (
"fmt"
"testing"
gomock "github.com/golang/mock/gomock"
)

func Test_getUser(t *testing.T) {
m := myfunc.CreateMyFunc() // 也就是说需要自己创建一个对象
v := getUser(m)
if v == "xiaomotong" {
fmt.Println("get user right!")
} else {
t.Error("get error user")
}
}

m := myfunc.CreateMyFunc() 看到上述这一句话,是创建对应的对象,再将该对象作为参数传入到 getUser 函数中,正常情况下这样做单测没有问题

但是如果这个时候创建 MyFunc 对象由于对外部还有依赖导致还没有编码好,可是也不能阻塞我们的单元测试

这个时候使用最上面的 mock 方案就显得尤为重要,可以使用 mock 的方式,mock 一个 MyFunc 对象,并设置好返回值即可完成,如:

1
2
3
go复制代码mockCtl := gomock.NewController(t)
mockMyFunc := NewMockMyFunc(mockCtl)
mockMyFunc.EXPECT().GetInfo().Return("xiaomotong")

执行上述代码结果如下:

1
2
3
4
shell复制代码> go test
get user right!
PASS
ok mygomock/myfunc 0.427s

感兴趣的朋友可以使用起来,用的多了就会更加熟悉

使用 gomock 的好处?

  • gomock 实现了较为完整的基于 interface 的 Mock 功能,能够与 Golang 内置的 testing包良好集成,也能用于其它的测试环境中
  • 学习成本低,很快就能上手

工具需要用起来,才能发挥他的价值,需要的可以用起来吧

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

好了,本次就到这里

常见技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是小魔童哪吒,欢迎点赞关注收藏,下次见~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

非主键索引更新引起的死锁 非主键索引更新引起的死锁

发表于 2021-10-30

非主键索引更新引起的死锁

  1. 问题

数据库日志抛出DeadLock
2. MySql update更新过程:

0. MySql 存储引擎


Innodb: 支持事务,更新时采用行级锁,并发性高


MyISAM: 不支持事务,更新时表锁,并发性差


因此使用Innodb才会发生死锁。
1. update更新过程


*行级锁*并不是直接锁记录,而是*锁索引*,如果一条SQL语句用到了主键索引,mysql会锁住主键索引;如果一条语句操作了非主键索引,mysql会先锁住非主键索引,再锁定主键索引。反之,


如果操作用到了主键索引会先在主键索引上加锁,然后在其他索引上加锁。


如果没有用到索引,则进行全表扫描,锁表。


当where条件为非主键索引,执行update时,会经过一下步骤:


1)先获取非主键索引的行级锁;


2)由数据库基本原理可知,where条件为非主键索引时,会发生回表查询,进而再获得主键索引的行级锁;


3)更新完毕,进行事务提交。
2. 根据上述步骤可知,对于非主键索引的update操作,其加锁过程并非原子操作,而且是分别需要获取不同索引的行级锁,这就为死锁带来了隐患:


假如,一条update语句用到主键索引和非主键索引,则获取锁的顺序是先获取主键索引,再获取非主键索引;而同时,另一条update语句只用到非主键索引,则获取锁的顺序是先获取非主键索引,再获取主键索引,二者正好发生在步骤 1)和 2)中间,则会造成锁。
  1. 解决方案

where条件加主键索引

先上锁查询查出来,在根据主键更新

逐条更新
4. 关于 行级锁并不是直接锁记录,而是锁索引的理解

由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的

1635579247735.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

细说一下RedisTemplate的使用方法(一)

发表于 2021-10-30

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

写在前面

我们在上篇文章中学习到了SpringBoot项目如何集成Redis相关组件功能,而SpringBoot集成Redis组件内部涉及的对象就是RedisTemplate。

接下来我们就一起来看一下RedisTemplate提供了哪些操作Redis数据库的方法,再者就是看一下这些方法是如何使用的。

一起来学习一下吧。

细数一下RedisTemplate系列方法。

就方法而言,我们还是在源码的基础上去看,这样一来可以看得全面一些,二来可以看到更多的东西,有助于更深层的理解。

afterPropertiesSet()

功能描述:初始化RedisTemplate的一些参数设置

使用场景:主要是在RedisTemplate初始化时进行调用,如果不执行此方法,可能会报一些莫名其妙的错误,那应该就是部分参数没有初始化造成的。

具体代码使用:

1
2
3
4
5
6
7
8
9
10
arduino复制代码@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
...
... 这中间省略掉了,大家可以去上篇文章里去找。
...
template.afterPropertiesSet();
return template;
}

注意事项:需要注意的一点就是SpringBoot项目中初始化RedisTemplate时一般都是通过配置类来执行一应方法,这个可以看一下上一篇文章。

源码截图:源码过长,可能不能贴全,请大家移步到IDEA中自行查看RedisTemplate.java

image.png

小结

今天我们就只是初学一下,先聊这一个方法的功能和使用情况,之后我们会针对这个系列每天都介绍一些有意思的方法,希望大家在此期间可以有所成长。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

《Java基础经典程序100例》(07)求 2/1+3/2+

发表于 2021-10-30

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动​​

求 2/1+3/2+5/3+8/5+13/8前 20 项之和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码package csdncom.tt;

/**
* Created by java李杨勇 on 2021/10/30.
*/
public class Sum {
public static void main(String[] args) {
double sum = 0;
double fenZi = 2.0, fenMu = 1.0; // 初始的分子 (fenZi)=2,分母(fenMu)=1
for (int i = 1; i <= 20; i++) {
sum += fenZi / fenMu;
fenMu = fenZi; // 下一项的分母 = 上一项的分子
fenZi += fenMu; // 下一项的分子 = 上一项的分子加分母
}
System.out.println("sum= " + sum);
}
}

打印:
sum= 40.0

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Terraform 基础设施即代码测试流程(一)

发表于 2021-10-30

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

为了满足持续集成持续部署,提高代码的健壮性,管理更多不同的云基础设施,需要通过测试代码提升代码将按预期执行的信心。

图片.png

一、测试范围

我们可通过不同工具的组合,执行不同类型的测试,以提供更广泛的代码覆盖率。总的测试范围包括:

  • 集成测试
  • 单元测试
  • 合规性测试
  • 端到端测试

在所有的测试工具中,总体来讲的比较优秀的有Terratest 和 TFLint。

二、集成测试

集成测试旨在测试所有代码或整个系统, 集成测试验证新引入的代码不会入侵现有的代码。

在工作流中使用以下测试方法并将它们包含在 CI 管道中将构成集成测试:

  • terraform fmt,命令执行验证是否正确格式化代码
  • terraform validate,命令执行验证语法是否正确
  • terraform plan,命令执行验证验证配置文件是否将按预期工作
  • TFLint,验证配置的内容以及语法和结构,还检查帐户限制(例如,VM 实例类型是否有效,以及是否未达到 Azure 中的 VM 数量限制)

除了上面提到的测试方法,我们还可以对代码进行静态分析。静态代码分析可以直接在 Terraform 配置代码上进行,无需执行。 此分析可用于检测安全问题和合规性不一致等问题,

并应该构成 CI 管道的一部分。 它通常可以在 CI 管道中的 Terraform 安装和初始化阶段之前完成。

能完成对Terraform 文件进行静态分析的工具包括:

  • Checkov
  • Terrascan
  • Tfsec
  • Deepsource

上面四种工具都提供对基础设施即代码的静态分析,其中一些并不仅仅支持支持Terraform。

Terratest 是一个流行的 Terraform 测试框架(也用于单元和 端到端的测试),测试代码是用 Go 编写的。 除了测试Terraform,其还可以用于测试 Packer、Docker、Kubernetes、

Vault 等许多其他产品。

另外,kitchen-terraform是一种比较受欢迎的 Terraform 集成测试框架。 它基于Ruby编程语言,有Chef经验的用户可能更熟悉,其还可用于测试操作系统级别的内容。

可参照Azure官方文档的集成测试-基于Azure的集成测试。

三、单元测试

单元测试旨在测试特定功能、多个功能或部分代码。 可以隔离基础设施的较小部分,并且可以并行运行测试以缩短反馈周期。

terraform plan是单元测试的一种形式——这可用于验证配置文件对于特定组件是否按预期工作, 但对于较大的项目,快速手动审查执行起来会相当困难。

Rspec和衍生框架(例如 Chef InSpec 或 ServerSpec)可用于执行测试驱动开发 (TDD), 它们是基于 Ruby 编程语言编写的。

Goss是一个被设计用于测试的工具,不是专门用于 Terraform 代码,而是用于测试结果(例如检查端口 22 是否打开并且可以访问),它是用 YAML 格式编写的。

同样,如上面讲到的,Terratest 也可用于单元测试(以及集成和 E2E 测试)。


少年,没看够?点击石头的主页,随便点点看看,说不定有惊喜呢?欢迎支持点赞/关注/评论,有你们的支持是我更文最大的动力,多谢啦!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

发表于 2021-10-30

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

————— 第二天 —————

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

————————————

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

. . . . . . . .

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

我们回到刚才的题目当中,假设背包的容量是10,有5个商品可供选择,每个商品的价值和重量如图所示:

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

让我们来计算一下每件物品的性价比,其结果如下:

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

毫无疑问,此时性价比最高的是物品4,我们把物品4放入背包当中,背包剩余的容量是8:

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

我们选择物品1放入背包,背包剩余的容量是4:

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

于是,我们选择0.8份的物品5放入背包,背包剩余的容量为0:

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

public static void main(String[] args) {

int capacity = 10;

int[] weights = {4,6,3,2,4};

int[] values = {9,3,1,6,4};

System.out.println(“背包最大价值:” + getHighestValue(capacity, weights, values));

}

public static double getHighestValue(int capacity, int[] weights,int[] values){

//创建物品列表并按照性价比倒序

List itemList = new ArrayList<>();

for(int i=0;i<weights.length;i++){

itemList.add(new Item(weights[i], values[i]));

}

itemList = itemList.stream().sorted(Comparator.comparing(Item::getRatio).reversed()).collect(Collectors.toList());

//背包剩余容量

int restCapacity = capacity;

//当前背包物品的最大价值

double highestValue = 0;

//按照性价比从高到低选择物品

for(Item item : itemList){

if(item.weight <= restCapacity){

highestValue += item.value;

restCapacity -= item.weight;

}else{

//背包装不下完整物品时,选择该件物品的一部分

highestValue += (double)restCapacity / (double)item.weight * item.value;

break;

}

}

return highestValue;

}

static class Item {

private int weight;

private int value;

//物品的性价比

private double ratio;

public Item (int weight, int value){

this.weight = weight;

this.value = value;

this.ratio = (double)value / (double)weight;

}

public double getRatio() {

return ratio;

}

}

在这段代码当中,我们借助了静态内部类Item,从而更方便地记录性价比、获取重量和价值信息、按性价比排序。

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

仍然给定一个容量是10的背包,有如下三个物品可供选择:

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

这一次我们有个条件限制:只允许选择整个物品,不能选择物品的一部分。

如果按照贪心算法的思路,首先选择的是性价比最高的物品1,那么背包剩余容量是4,再也装不下其他物品,而此时的总价值是6:

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

但这样的选择,真的能让总价值最大化吗?如果我们不选择物品1,选择物品2和物品3的话,剩余容量是0,总价值是7:

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

显然,7>6,依靠贪心算法得出的结果,未必是全局最优解。

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

对于01背包问题,我们可以使用动态规划算法来求解,有兴趣的小伙伴可以微信搜索“程序员小灰”,里面有关于动态规划算法的讲解。

漫画:什么是“贪心算法”?如何求解“部分背包问题”?

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

使用Springprofile实现开发、测试和生产环境的配

发表于 2021-10-30

介绍

软件开发过程一般涉及“开发 -> 测试 -> 部署上线”多个阶段,每个阶段的环境的配置参数会有不同,如数据源,文件路径等。为避免每次切换环境时都要进行参数配置等繁琐的操作,可以通过spring的profile功能来进行配置参数的切换。

以我用到的项目的实际情况为例,首先可以在resources文件夹下分别为每个环境建立单独的文件夹(也可以额外建立一个common文件夹,用于存放公共的参数配置文件),每个文件夹下面存放对应的环境所需的配置文件,就像这样子:

使用Spring.profile实现开发、测试和生产环境的配置和切换

配置和使用

在resources文件夹下建立

applicationContext-profile.xml文件,用来定义不同的profile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<beans> <!--省略了一些头部属性-->
<description>spring profile配置</description>

<!-- 开发环境配置文件 -->
<beans profile="development">
<context:property-placeholder
location="classpath*:common/*.properties, classpath*:development/*.properties" />
</beans>

<!-- 测试环境配置文件 -->
<beans profile="test">
<context:property-placeholder
location="classpath*:common/*.properties, classpath*:test/*.properties" />
</beans>

<!-- 生产环境配置文件 -->
<beans profile="production">
<context:property-placeholder
location="classpath*:common/*.properties, classpath*:production/*.properties" />
</beans>
</beans>

这样就实现了通过profile标记不同的环境,接下来就可以通过设置spring.profiles.default和spring.profiles.active这两个属性来激活和使用对应的配置文件。default为默认,如果没有通过active来指定,那么就默认使用default定义的环境。

这两个属性可以通过多种方法来设置:

  • 在web.xml中作为web应用的上下文参数context-param;
  • 在web.xml中作为DispatcherServlet的初始化参数;
  • 作为JNDI条目;
  • 作为环境变量;
  • 作为JVM的系统属性;
  • 在集成测试类上,使用@ActiveProfiles注解配置。

前两者都可以在web.xml文件中设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"><!--省略了一些头部属性-->
<display-name>Archetype Created Web Application</display-name>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/applicationContext*.xml
</param-value>
</context-param>

<!-- 在上下文context-param中设置profile.default的默认值 -->
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>development</param-value>
</context-param>

<!-- 在上下文context-param中设置profile.active的默认值 -->
<!-- 设置active后default失效,web启动时会加载对应的环境信息 -->
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>development</param-value>
</context-param>

<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 在DispatcherServlet参数中设置profile的默认值,active同理 -->
<init-param>
<param-name>spring.profiles.default</param-name>
<param-value>development</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

</web-app>

激活指定的环境,也可以通过JVM参数来设置,可以在tomcat的启动脚本中加入以下JVM参数来激活:

使用Spring.profile实现开发、测试和生产环境的配置和切换

1
ini复制代码-Dspring.profiles.active="production"

在程序中,也可以通过 @Profile(“…”) 对某些资源进行注解,这样只有当选择对应的环境时,才会产生对应的bean,如:

1
2
3
4
5
6
7
8
9
10
less复制代码@Bean
@Profile("production")
public DataSource jndiDataSource(){
JndiObjectFactoryBean jofb=new JndiObjectFactoryBean();
jofb.setJndiName("jndi/iDS");
jofb.setResourceRef(true);
jofb.setProxyInterface(xxx.class);
return (DataSource) jofb.getObject();
}
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

发表于 2021-10-30

今天介绍一下IDEA的一些炫酷的插件,IDEA强大的插件库,不仅能给我们带来一些开发的便捷,还能体现我们的与众不同。

1.插件的安装

打开setting文件选择Plugins选项

  • Ctrl + Alt + S
  • File -> Setting

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

分别是安装JetBrains插件,第三方插件,本地已下载的插件包。详情见往期关于settings的文章。

2.各种插件

#1. activate-power-mode 和 Power mode II

根据Atom的插件activate-power-mode的效果移植到IDEA上

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

写代码是整个屏幕都在抖动,activate-power-mode是白的的,Power mode II色彩更酷炫点。

#2.Background Image Plus

idea背景修改插件,让你的idea与众不同,可以设置自己喜欢的图片作为code背景。

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

安装成功之后重启,菜单栏的VIew标签>点击Set Background Image(没安装插件是没有这个标签的),在弹框中路由选择到本地图片,点击OK即可。

#3.Grep console

自定义日志颜色,idea控制台可以彩色显示各种级别的log,安装完成后,在console中右键就能打开。

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

并且可以设置不同的日志级别的显示样式。

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

可以直接根据关键字搜索你想要的,搜索条件是支持正则表达式的。官网地址。

#4.Free Mybatis plugin

mybatis 插件,让你的mybatis.xml像java代码一样编辑。我们开发中使用mybatis时时长需要通过mapper接口查找对应的xml中的sql语句,该插件方便了我们的操作。

安装完成重启IDEA之后,我们会看到code左侧或多出一列绿色的箭头,点击箭头我们就可以直接定位到xml相应文件的位置。

mapper

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

xml

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

#5.MyBatis Log Plugin

Mybatis现在是java中操作数据库的首选,在开发的时候,我们都会把Mybatis的脚本直接输出在console中,但是默认的情况下,输出的脚本不是一个可以直接执行的。

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

如果我们想直接执行,还需要在手动转化一下。

MyBatis Log Plugin 这款插件是直接将Mybatis执行的sql脚本显示出来,无需处理,可以直接复制出来执行的,如图:

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

执行程序后,我们可以很清晰的看到我们执行了哪些sql脚本,而且脚本可以执行拿出来运行。

#6.String Manipulation

强大的字符串转换工具。使用快捷键,Alt+m。

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

  • 切换样式(camelCase, hyphen-lowercase, HYPHEN-UPPERCASE, snake_case, SCREAMING_SNAKE_CASE, dot.case, words lowercase, Words Capitalized, PascalCase)
  • 转换为SCREAMING_SNAKE_CASE (或转换为camelCase)
  • 转换为 snake_case (或转换为camelCase)
  • 转换为dot.case (或转换为camelCase)
  • 转换为hyphen-case (或转换为camelCase)
  • 转换为hyphen-case (或转换为snake_case)
  • 转换为camelCase (或转换为Words)
  • 转换为camelCase (或转换为lowercase words)
  • 转换为PascalCase (或转换为camelCase)
  • 选定文本大写
  • 样式反转

#7.Alibaba Java Coding Guidelines

阿里巴巴代码规范检查插件,当然规范可以参考《阿里巴巴Java开发手册》。

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

#8.Lombok

Java语言,每次写实体类的时候都需要写一大堆的setter,getter,如果bean中的属性一旦有修改、删除或增加时,需要重新生成或删除get/set等方法,给代码维护增加负担,这也是Java被诟病的一种原因。Lombok则为我们解决了这些问题,使用了lombok的注解(@Setter,@Getter,@ToString,@@RequiredArgsConstructor,@EqualsAndHashCode或@Data)之后,就不需要编写或生成get/set等方法,很大程度上减少了代码量,而且减少了代码维护的负担。

安装完成之后,在应用Lombok的时候注意别忘了需要添加依,maven为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typescript复制代码<dependency> 
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>


@Setter
@Getter
@ToString
@EqualsAndHashCode
public class People {
private String name;
private int age;
private String male;
}

#9.Key promoter

Key promoter 是IntelliJ IDEA的快捷键提示插件,会统计你鼠标点击某个功能的次数,提示你应该用什么快捷键,帮助记忆快捷键,等熟悉了之后可以关闭掉这个插件。

#10.Gsonformat

可根据json数据快速生成java实体类。

自定义个javaBean(无任何内容,就一个空的类),复制你要解析的Json,然后alt+insert弹出如下界面或者使用快捷键 Alt+S,在里面粘贴刚刚复制的Json,点击OK即可。

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

#11.Restfultookit

Spring MVC网页开发的时候,我们都是通过requestmapping的方式来定义页面的URL地址的,为了找到这个地址我们一般都是cmd+shift+F的方式进行查找,大家都知道,我们URL的命名一个是类requestmapping+方法requestmapping,查找的时候还是有那么一点不方便的,restfultookit就能很方便的帮忙进行查找。

例如:我要找到/user/add 对应的controller,那么只要Ctrl+斜杠 ,(图片来自于网络)

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

就能直接定位到我们想要的controller。这个也是真心方便,当然restfultookit还为我们提供的其他的功能。根据我们的controller帮我们生成默认的测试数据,还能直接调用测试,这个可以是解决了我们每次postman调试数据时,自己傻傻的组装数据的的操作,这个更加清晰,比在console找数据包要方便多了。(图片来自于网络)

IntelliJ IDEA,酷炫插件系列,提高你的工作效率

#12.JRebel

JRebel是一种热部署生产力工具,修改代码后不用重新启动程序,所有的更改便可以生效。它跳过了Java开发中常见的重建、重新启动和重新部署周期。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Linux的基础命令汇总,可以收藏一波

发表于 2021-10-30

linux命令是对Linux系统进行管理的命令。对于Linux系统来说,无论是中央处

理器、内存、磁盘驱动器、键盘、鼠标,还是用户等都是文件,Linux系统管理

的命令是它正常运行的核心。

线上查询及帮助命令(2个)

man:查看命令帮助,命令的词典,更复杂的还有info,但不常用。

help:查看Linux内置命令的帮助,比如cd命令。

文件和目录操作命令(18个)

ls:全拼list,功能是列出目录的内容及其内容属性信息。

cd:全拼change directory,功能是从当前工作目录切换到指定的工作目录。

cp:全拼copy,其功能为复制文件或目录。

find:查找的意思,用于查找目录及目录下的文件。

mkdir:全拼make directories,其功能是创建目录。

mv:全拼move,其功能是移动或重命名文件。

pwd:全拼print working directory,其功能是显示当前工作目录的绝对路径。

rename:用于重命名文件。

rm:全拼remove,其功能是删除一个或多个文件或目录。

rmdir:全拼remove empty directories,功能是删除空目录。

touch:创建新的空文件,改变已有文件的时间戳属性。

tree:功能是以树形结构显示目录下的内容。

basename:显示文件名或目录名。

dirname:显示文件或目录路径。

chattr:改变文件的扩展属性。

lsattr:查看文件扩展属性。

file:显示文件的类型。

md5sum:计算和校验文件的MD5值。

查看文件及内容处理命令(21个)

cat:全拼concatenate,功能是用于连接多个文件并且打印到屏幕输出或重定向到指定文件中。

tactac:是cat的反向拼写,因此命令的功能为反向显示文件内容。

more:分页显示文件内容。

less:分页显示文件内容,more命令的相反用法。

head:显示文件内容的头部。

tail:显示文件内容的尾部。

cut:将文件的每一行按指定分隔符分割并输出。

split:分割文件为不同的小片段。

paste:按行合并文件内容。

sort:对文件的文本内容排序。

uniq:去除重复行。

wc:统计文件的行数、单词数或字节数。

iconv:转换文件的编码格式。

dos2unix:将DOS格式文件转换成UNIX格式。

diff:全拼difference,比较文件的差异,常用于文本文件。

vimdiff:命令行可视化文件比较工具,常用于文本文件。

rev:反向输出文件内容。

grep/egrep:过滤字符串,三剑客老三。

join:按两个文件的相同字段合并。

tr:替换或删除字符。

vi/vim:命令行文本编辑器。

文件压缩及解压缩命令(4个)

tar:打包压缩。oldboy

unzip:解压文件。

gzipgzip:压缩工具。

zip:压缩工具。

信息显示命令(11个)

uname:显示操作系统相关信息的命令。

hostname:显示或者设置当前系统的主机名。

dmesg:显示开机信息,用于诊断系统故障。

uptime:显示系统运行时间及负载。

stat:显示文件或文件系统的状态。

du:计算磁盘空间使用情况。

df:报告文件系统磁盘空间的使用情况。

top:实时显示系统资源使用情况。

free:查看系统内存。

date:显示与设置系统时间。

cal:查看日历等时间信息。

搜索文件命令(4个)

which:查找二进制命令,按环境变量PATH路径查找。

find:从磁盘遍历查找文件或目录。

whereis:查找二进制命令,按环境变量PATH路径查找。

locate:从数据库 (/var/lib/mlocate/mlocate.db) 查找命令,使用updatedb更新库。

用户管理命令(10个)

useradd:添加用户。

usermod:修改系统已经存在的用户属性。

userdel:删除用户。

groupadd:添加用户组。

passwd:修改用户密码。

chage:修改用户密码有效期限。

id:查看用户的uid,gid及归属的用户组。

su:切换用户身份。

visudo:编辑/etc/sudoers文件的专属命令。

sudo:以另外一个用户身份(默认root用户)执行事先在sudoers文件允许的命令。

基础网络操作命令(11个)

telnet:使用TELNET协议远程登录。

ssh:使用SSH加密协议远程登录。

scp:全拼secure copy,用于不同主机之间复制文件。

wget:命令行下载文件。

ping:测试主机之间网络的连通性。

route:显示和设置linux系统的路由表。

ifconfig:查看、配置、启用或禁用网络接口的命令。

ifup:启动网卡。

ifdown:关闭网卡。

netstat:查看网络状态。

ss:查看网络状态。

深入网络操作命令(9个)

nmap:网络扫描命令。

lsof:全名list open files,也就是列举系统中已经被打开的文件。

mail:发送和接收邮件。

mutt:邮件管理命令。

nslookup:交互式查询互联网DNS服务器的命令。

dig:查找DNS解析过程。

host:查询DNS的命令。

traceroute:追踪数据传输路由状况。

tcpdump:命令行的抓包工具。

磁盘与文件系统的命令(16个)

mount:挂载文件系统。

umount:卸载文件系统。

fsck:检查并修复Linux文件系统。

dd:转换或复制文件。

dumpe2fs:导出ext2/ext3/ext4文件系统信息。

dumpe:xt2/3/4文件系统备份工具。

fdisk:磁盘分区命令,适用于2TB以下磁盘分区。

parted:磁盘分区命令,没有磁盘大小限制,常用于2TB以下磁盘分区。

mkfs:格式化创建Linux文件系统。

partprobe:更新内核的硬盘分区表信息。

e2fsck:检查ext2/ext3/ext4类型文件系统。

mkswap:创建Linux交换分区。

swapon:启用交换分区。

swapoff:关闭交换分区。

sync:将内存缓冲区内的数据写入磁盘。

resize2fs:调整ext2/ext3/ext4文件系统大小。

系统权限及用户相关命令(4个)

chmod:改变文件或目录权限。

chown:改变文件或目录的属主和属组。

chgrp:更改文件用户组。

umask:显示或设置权限掩码。

查看系统用户信息的命令(7个)

whoami:显示当前有效的用户名称,相当于执行id -un命令。

who:显示目前登录系统的用户信息。

w:显示已经登陆系统的用户列表,并显示用户正在执行的指令。

last:显示登入系统的用户。

lastlog:显示系统中所有用户最近一次登录信息。

users:显示当前登录系统的所有用户的用户列表。

finger:查找并显示用户信息。

内置命令及其它(19个)

echo:打印变量,或直接输出指定的字符串

printf:将结果格式化输出到标准输出。

rpm:管理rpm包的命令。

yum:自动化简单化地管理rpm包的命令。

watch:周期性的执行给定的命令,并将命令的输出以全屏方式显示。

alias:设置系统别名。

unalias:取消系统别名。

date:查看或设置系统时间。

clear:清除屏幕,简称清屏。

history:查看命令执行的历史纪录。

eject:弹出光驱。

time:计算命令执行时间。

nc:功能强大的网络工具。

xargs:将标准输入转换成命令行参数。

exec:调用并执行指令的命令。

export:设置或者显示环境变量。

unset:删除变量或函数。

type:用于判断另外一个命令是否是内置命令。

bc:命令行科学计算器。

系统管理与性能监视命令(9个)

chkconfig:管理Linux系统开机启动项。

vmstat:虚拟内存统计。

mpstat:显示各个可用CPU的状态统计。

iostat:统计系统IO。

sar:全面地获取系统的CPU、运行队列、磁盘 I/O、分页(交换区)、内存、 CPU中断和网络等性能数据。

ipcs:用于报告Linux中进程间通信设施的状态,显示的信息包括消息列表、共享内存和信号量的信息。

ipcrm:用来删除一个或更多的消息队列、信号量集或者共享内存标识。

strace:用于诊断、调试Linux用户空间跟踪器。我们用它来监控用户空间进程和内核的交互,比如系统调用、信号传递、进程状态变更等。

ltrace:命令会跟踪进程的库函数调用,它会显现出哪个库函数被调用。

关机/重启/注销和查看系统信息的命令(6个)

shutdown:关机。

halt:关机。

poweroff:关闭电源。

logout:退出当前登录的Shell。

exit:退出当前登录的Shell。

Ctrl+d:退出当前登录的Shell的快捷键。

进程管理相关命令(15个)

bg:将一个在后台暂停的命令,变成继续执行 (在后台执行)。

fg:将后台中的命令调至前台继续运行。

jobs:查看当前有多少在后台运行的命令。

kill:终止进程。

killall:通过进程名终止进程。

pkill:通过进程名终止进程。

crontab:定时任务命令。

ps:显示进程的快照。

pstree:树形显示进程。

nice/renice:调整程序运行的优先级。

nohup:忽略挂起信号运行指定的命令。

pgrep:查找匹配条件的进程。

runlevel:查看系统当前运行级别。

init:切换运行级别。

service:启动、停止、重新启动和关闭系统服务,还可以显示所有系统服务的当前状态。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

C语言-strlen和sizeof笔试题- II

发表于 2021-10-30

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金

前言:🛫初来掘金!欢迎大佬们支持!相互学习,相互成长!


续上文:I

strlen和sizeof概念上的区别:[C语言-strlen与sizeof区别 - 掘金 (juejin.cn)]


题目3:字符数组

char arr[] = “abcdef”

image.png


strlen()相关题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
arduino复制代码char arr[] = "abcdef";
//此时数组arr中存放了\0 strlen求长度,遇到\0即停止计数

printf("%d\n", strlen(arr));//从arr位置开始向后计数,遇到\0即停,长度为6

printf("%d\n", strlen(arr+0));///从arr位置开始向后计数,遇到\0即停,长度为6
//printf("%d\n", strlen(*arr));//arr是首元素地址,*arr是字符‘a’对应ascii值为97,strlen把字符a对应的ascii码值97作为地址向后计数,非法访问!err
//printf("%d\n", strlen(arr[1]));///arr[1]:‘b’对应ascii值为98,strlen把字符b对应的ascii码值98作为地址向后计数,非法访问!err

printf("%d\n", strlen(&arr));&arr和arr地址值相同,都是首元素地址,但是意义不一样
//&arr传给strlen &arr类型:数组指针 char(*p)[6] 而strlen接收的类型为char*,不兼容,但是问题不大
//从数组首元素位置向后计数,值为 6

printf("%d\n", strlen(&arr+1));//跳过整个数组后,向后计数,未知值

printf("%d\n", strlen(&arr[0]+1));//从b未知向后计数,长度为5

sizeof()相关题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
c复制代码char arr[] = "abcdef";
//此时的arr数组里面是放了\0的

printf("%d\n", sizeof(arr));//数组名单独放在sizeof内部,计算的是整个数组的大小,\0也算进去,大小为7

printf("%d\n", sizeof(arr+0));//此时的数组名是首元素地址,地址(指针)大小:4/8

printf("%d\n", sizeof(*arr));//此时的数组名是首元素地址,*arr即为首元素,字符a->char类型,大小为1

printf("%d\n", sizeof(arr[1]));//arr[1]:字符'b',大小为1

printf("%d\n", sizeof(&arr));//取出数组的地址,还是地址,大小为4/8

printf("%d\n", sizeof(&arr+1));//取出数组的地址+1,跳过整个数组,还是地址:4/8

printf("%d\n", sizeof(&arr[0]+1));//取出第一个元素的地址+1,跳过一个元素,即为第二个元素的地址,4/8

题目4: 指针指向的常量字符串

char *p = “abcdef”

image.png

strlen()相关题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
arduino复制代码const char* p = "abcdef";
//p存放的是字符a的地址,
//p+1:字符b的地址

printf("%d\n", strlen(p));//p存放的是字符a的地址,即从字符a的地址向后计数,长度为6

printf("%d\n", strlen(p+1));//从字符b的地址向后计数,长度为5

//printf("%d\n", strlen(*p));//*p ->字符‘a’ 即以字符a的ascii码值97为地址向后计数,非法访问,err

//printf("%d\n", strlen(p[0]));//p[0] ->字符‘a’ 即以字符a的ascii码值97为地址向后计数,非法访问,err

printf("%d\n", strlen(&p));//&p取出的是p变量的地址,即以p变量的地址(16进制)向后计数, 随机值

printf("%d\n", strlen(&p+1));//&p取出的是p变量的地址,&p+1,跳过p变量,即从p变量之后的位置向后访问 随机值

printf("%d\n", strlen(&p[0]+1));//&p[0]==>相当于&*(p+0)-->相当于sizeof(p),p存中存放的是字符a的地址,+1,即为字符b的地址,从字符b位置向后访问, 长度为5
// p[0] :字符a
//&p[0]:字符a的地址
//&p[0] +1:字符b的地址

//注意上面两个随机值没有必然关系,因为strlen(&p)是以p地址对应的16进制向后访问,值为未知数,有可能提前遇到\0结束了

sizeof()相关题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
c复制代码//因为指针指向的是常量字符串,不可以被修改
//所以可以用const修饰
//char* p = "abcdef";
const char * p ="abcdef";
//p存放的是字符a的地址

printf("%d\n", sizeof(p));//p是指针,指向字符a,大小为4/8

printf("%d\n", sizeof(p+1));//p+1,指向的是字符b,指针,大小为4/8

printf("%d\n", sizeof(*p));//p存放的是字符a的地址,*p:即为字符a,大小为1

printf("%d\n", sizeof(p[0]));//p[0]->字符a ,大小为1

printf("%d\n", sizeof(&p));//取出p变量的地址,仍是地址,大小为4/8

printf("%d\n", sizeof(&p+1));//取出p变量的地址+1,跳过p变量,但仍是地址,大小为4/8

printf("%d\n", sizeof(&p[0]+1));//&p[0]相当于&*(p+0),&和*抵消,&p[0]:字符a的地址,+1:字符b的地址,大小为4/8

题目5:二维数组

int a[3][4]

内存布局

image.png

sizeof()相关题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
css复制代码//二维数组
int a[3][4] = {0};

printf("%d\n",sizeof(a));//数组名单独放在sizeof内部,计算的是整个数组的大小, 数组元素为12个,每一个元素大小为4个字节,12*4=48

printf("%d\n",sizeof(a[0][0]));//计算的是数组第有一行第一个元素的大小,int类型,大小为4

printf("%d\n",sizeof(a[0]));//a[0]==>*(a+0)==>数组名是首元素地址,即为第一行的地址,解引用第一行的地址,就是第一行,所以计算的是第一行元素的大小 4*4=16
//a[0] : 二维数组的第一行

printf("%d\n",sizeof(a[0]+1));//a[0]:第一行的数组名,代表第一行第一个元素的地址,a[0]+1::跳过一个元素,即为第一行第二个元素地址,大小为4/8
//注意:a[0] + 1 :不是第二行,a[0]是第一行的数组名,首元素地址,即为第一行第一个元素地址,a[0]+1:跳过一个元素 a+1:a为数组名,首元素地址,第一行的地址,a+1,跳过一行,二维数组第二行

printf("%d\n",sizeof(*(a[0]+1)));//由上可得:a[0]+1:第一行第二个元素地址, *(a[0]+1):即为第一行第二个元素 int类型 大小为4

printf("%d\n",sizeof(a+1));//a为二维数组的数组名->首元素地址,e二维数组第一行的地址,+1,跳过一行,即为第二行的地址->地址,大小为4/8

printf("%d\n",sizeof(*(a+1)));//由上,a+1是第二行的地址,*(a+1)即为第二行,大小为4*4 = 16

printf("%d\n",sizeof(&a[0]+1));//a[0]是第一行的数组名,&a[0]就是第一行的地址,(相当于是,数组名和取地址数组名的关系,二二者地址值相同,但是含义不同),&a[0]+1:跳过第一行,即为第二行地址,地址:4/8

printf("%d\n",sizeof(*(&a[0]+1)));//由上:(&a[0]+1):第二行地址,解引用就是第二行,大小为4*4 = 16

printf("%d\n",sizeof(*a));//二维数组数组名是首元素地址,即为第一行的地址,解引用就是第一行, 大小为4* 4 = 16

printf("%d\n",sizeof(a[3]));//a[3]假设存在,就是第四行的数组名,sizeof(a[3])相当于数组名单独放在sizeof内部,计算的是第四行的大小, 4*4 = 16

//sizeof内部的表达式不参与运算,即不会真的去访问a[3]的空间,所以不出错,它只看一下第四行的类型,并没有真正去访问第四行的内容,

💖💕strlen和sizeof习题讲解Over~

strlen()和sizeof()坑点挺多的,大家认真学习!一起加油哈哈哈!

如果感觉对你有帮助的,欢迎点个赞评论一下呀!

欢迎大佬们点赞、收藏、评论呀!笔者水平有限,欢迎各位大佬批评指正!再次感谢
💥希望本文章对你有所帮助!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…451452453…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%