《合伙人制度——有效激励而不失控制权是怎样实现的》

合伙人制度是目前一些企业常用的用人和资本运作的方法,好的合伙人制度可以激发员工的工作热情同时也保证了公司的稳定安全。在现有公司下我对合伙人制度这个东西深有体会。

合伙人制度——有效激励而不失控制权是怎样实现的

触发的工作问题:

领导的信任,给了我上升空间,和投资项目的一些机会。作为一名员工对公司的合伙人机制基本是空白,抱着综合成长和对合伙人体制的刨根心态我选择了此书

##书中核心观点:
1.合伙人制度是双赢的制度设计
2.合理的合伙人设计即有效激励也不会失控。
3.介绍并强调开公司做企业的一定要学会看年报
4.股权的游戏规则有很多讲究
5.大量案例佐证合伙人制度在当下的重要性

##实践应用案例及效果:
实践应用案例:XXX定投合伙
效果:几次会议下来的名词在书中得到了相应的解释,解答了心中的疑惑。也明白了为什么企业想让员工成为合伙人,并且员工自己需要做什么该做什么。对公司合伙人文化有了更深一步的认知,获益匪浅。

Go语言的类型系统-结构体与方法

Go语言可以让用户定义类型,当用户声明一个新类型时,这个声明就给编译器提供了一个框架,告知必要的内存大小和表示信息。

结构体

声明结构体的关键字 struct 与C一样一样的,处处都是C的影子。

1
2
3
4
type user struct {
name string
email string
}

var 表示一个变量并设置为零值,:= 一个短变量声明操作符,声明一个变量并初始化。

1
var zjj user
1
2
3
4
zjj := user{
name: "zjj",
email: "xxxx@qq.com",
}

上面演示了一种给结构体赋值的方式,还有一种是。需要注意的是上面类似于json的书写方式结尾要保留一个,下面不用

1
zjj := user{ "zjj", "xxxx@qq.com"}

方法

方法能给用户定义的类型添加新的行为,方法实际上也是函数,只是在声明时,在关键字func和方法名之间增加了一个参数

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
package main

import "fmt"

type user struct {
name string
email string
}

//使用值接收者实现的一个方法
func (u user) notify(){
fmt.Printf("%s同学的邮箱为:%s\n",u.name,u.email)
}

//使用指针接受者实现的方法
func (u *user) changEmail(e string){
u.email = e
}

func main() {
zjj := user{name:"zjj",email:"222@qq.com"}
zjj.changEmail("1111")
zjj.notify()


dx := &user{name:"dx",email:"111@qq.com"}
dx.changEmail("2222")
dx.notify()

}

  • 使用值接收者声明,调用时会用这个值的一个副本来执行
1
2
dx := &user{name:"dx",email:"111@qq.com"}
dx.notify()

Go在背后偷摸着这样实现了指针的解引操作

1
(*dx).notify
  • 指针接收者声明,接收者的类型是user的指针而不是值,使用这种方法时会共享修改内容。
1
2
3
4
//使用指针接受者实现的方法
func (u *user) changEmail(e string){
u.email = e
}

go语言的数组切片和映射

PHP的数组我感觉是最好用的数据结构了,开发完全不用担心它在内存上做了什么,相比下C语言中的数组就需要开发另外操心做一些内存上的处理。现在我们来了解下GO中的数组,切片和映射,你会发现Go语言的切片和映射都是基于的数组实现的。数组的容量即是声明时的容量,不可以再做更改,切片有容量的限制不过可以使用append函数扩容,映射没有容量或者任何限制。切片和映射传递的成本都很小,并且不会复制底层数据结构,通俗理解是Go底层帮我们维护了一个底层数组,切片和映射只是指向这个底层数组的内存地址。

数组

数组在内存中是连续分配的,因为连续分配CPU能把正在使用的数据缓存更久也更容易计算索引。

声明和初始化

声明一个数组,并设置为零值

1
2
//声明一个包含5个元素的整型数组
var array [5]int
  • 一旦声明,数组里储存的数据类型和数组长度就都不能改变
  • go语言数组初始化,每个元素对数对应类型的零值

使用数组字面声明数组

1
2
3
//声明包含5个元素的整型数组
//用具体值初始化每个元素
array := [5]int{1,2,3,4,5}

让Go自动计算声明的数据长度

1
2
3
4
//声明包含5个元素的整型数组
//用具体值初始化每个元素
//容量由初始化的数量决定
array := [...]int{1,2,3,4,5}

声明数组并指定特定元素的值

1
2
3
4
//声明一个5个元素的数组
//用具体值初始化元素
//其余元素为0
array := [5]int{3:5,4:2}

使用数组

访问数组元素

1
2
3
4
5
6
7
8
array := [...]int{1,2,3}
//修改索引为2的元素的值
array[2] = 1
//range迭代打印数组元素
for key,value := range array{
fmt.Printf("key:%d,value:%d\n",key,value)
}

访问指针数组元素

1
2
3
4
5
6
7
8
9

//声明一个包含5个元素的指向整数的数组
//用整数指针初始化索引为0的元素
array := [5]*int{0:new(int)}
//为索引0的元素赋值
*array[0] = 10
//解引索引0的值
fmt.Print(*array[0])

多维数组

1
2
3
4
5
6
7
8
9
10
11
//声明一个多维数组,两个维度分别储存4个元素和2个元素
var array [4][2]int

//使用数组字面量来声明并初始化一个二维数组
array := [4][2]int{{1,2},{3,4},{5,6},{7,8}}

//声明并初始化外层数组中索引为1和3的元素
array := [4][2]int{1:{1,2},3:{2,3}}

//声明并初始化外层数组和内层数组的单个元素
array := [4][2]int{1:{0:20}}

使用指针在函数内传值

指针的概念是指向一个值的内存地址,理解了这个概念我们就会知道,用指针来传递值可以减少内存的开销。

1
2
3
4
5
6
7
8

func main() {
//分配一个需要8MB的数组
var array [1e6]int
//讲数组的地址传递给foo函数
foo(&array)


切片

切片是一种数据结构,这种数据结构便于使用和管理数据集合。切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。切片有三个参数 分别是 指向底层数组的指针切片访问元素个数允许增长到的元素个数

创建和初始化

make声明切片

使用长度声明一个字符串切片

1
2
//创建一个字符串切片,其长度和容量都是5个元素
slice := make([]string,5)

使用长度和容量声明整型切片

1
2
//创建一个int切片,其长度3容量为5
slice := make([]int,3,5)

通过切片字面量来声明切片

1
2
//创建一个内容3个元素的字符串切片
slice := []string{"red","blue","green"}
1
2
//创建一个容量5个元素的整型切片
slice := []int{1,2,3,4,5}

使用索引声明切片

1
2
//创建字符串切片,使用空字符串初始化第100个元素
slice := []string{99:""}

记住[]中有了值就不是切片了是数组

使用切片

切片是在数组的基础上丰富的一个对象,切片操作和数组操也很像。[]写入了数量是数组,切片之所以是切片因为创建一个新的切片就是把底层数组切出一部分。

1
2
3
4
5
6
7
8
//创建一个长度容量为5的切片
slice := []int{1,2,3,4}
//创建一个长度为2容量为4的新切片
newSlice := slice[1:3]
//打印
for k,v := range(newSlice){
fmt.Printf("k=%d,v=%d \n",k,v)
}

如何计算长度和容量

对底层数组容量是K的切片 slice[i:j]

长度:j-i
容量:k-i

切片创建的切片修改值

切片创建的切片其实用的是同一份内存地址,即新老切片同时指向一个内存地址。内存地址中的值更改了切片的内容也改了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//创建一个长度容量为5的切片
slice := []int{1,2,3,4}
//创建一个长度为2容量为4的新切片
newSlice := slice[1:3]

//修改新切片的第一个元素
newSlice[0] = 10

for k,v := range(slice){
fmt.Printf("k=%d,v=%d \n",k,v)
}

fmt.Print("------- \n")

for k,v := range(newSlice){
fmt.Printf("k=%d,v=%d \n",k,v)
}

输出

1
2
3
4
5
6
7
8
k=0,v=1
k=1,v=10
k=2,v=3
k=3,v=4
-------
k=0,v=10
k=1,v=3

切片增长(append)

相对数组而言,使用切片的一个好处是,可以按需增加切片的容量。Go内置的append函数会处理增加长度时所有操作细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//创建一个长度容量为5的切片
slice := []int{1,2,3,4}
//创建一个长度为2容量为4的新切片
newSlice := slice[1:3]

//newSlice切片新增一个元素
newSlice = append(newSlice,60)


for k,v := range(slice){
fmt.Printf("k=%d,v=%d \n",k,v)
}

fmt.Print("------- \n")

for k,v := range(newSlice){
fmt.Printf("k=%d,v=%d \n",k,v)
}

输出

1
2
3
4
5
6
7
8
9
k=0,v=1 
k=1,v=2
k=2,v=3
k=3,v=60
-------
k=0,v=2
k=1,v=3
k=2,v=60

可以看到append在底层数组还有额外容量可用的情况下会直接修改底层数组,在不可用的情况下会创建一个新的底层数组并把值复制过去再追加新的内容。追加规则为1000以下X2,1000以上X1.25即增长25%

设置长度和容量一样的切片

如上可见,append会修改底层数组造成数据上的污染。长度和容量一样的好处是可以避免这种污染,它会创建一个属于自己的底层数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//创建一个长度容量为5的切片
slice := []int{1,2,3,4}
//创建一个长度为2容量为4的新切片
newSlice := slice[1:3:3]//注意这里切片的第3个值

//newSlice切片新增一个元素
newSlice = append(newSlice,60)


for k,v := range(slice){
fmt.Printf("k=%d,v=%d \n",k,v)
}

fmt.Print("------- \n")

for k,v := range(newSlice){
fmt.Printf("k=%d,v=%d \n",k,v)
}

输出结果

1
2
3
4
5
6
7
8
k=0,v=1 
k=1,v=2
k=2,v=3
k=3,v=4 //数据未被修改
-------
k=0,v=2
k=1,v=3
k=2,v=60 //追加上了新值

迭代切片 for range

前面有用到过不详细说明了

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
sclie := []string{"aaa","bbb","ccc"}

for k,v := range(sclie){
fmt.Printf("k=%d,v=%s\n",k,v)
}
}

原始实现

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
sclie := []string{"aaa","bbb","ccc"}

for index := 0;index <len(sclie) ; index++ {
fmt.Printf("k=%d,v=%s\n",index,sclie[index])
}
}

多维切片

和多维数组的玩法一样,差别就是别指定个数

1
2
3
4
//创建一个整型切片
slice := [][]int{{10},{100,200}}
//为第一个切片追加值为20的元素
slice[0] = append(slice[0],20)

在函数间传递切片

切片是底层数组的解引,所以不用像数组一样加上&地址符,切片本身的数据关联在底层数组里,所以讲切片复制到任意函数,对底层数据大小都不会有影响。

映射

映射是一种数据结构,用于存储一系列无序的键值对。映射会在散列表中进行一些操作高效的找到键值对

创建和初始化

1
2
3
4
5
6
//创建一个映射,键的类型是string,值的类型是int
dict :=make(map[string]int)
//创建一个映射,键和值类型都为string并初始化
dict := map[string]string{"red":"#da1337"}
//创建一个映射,使用字符串切片为值
dict := map[string][]string{}

使用映射

打印

创建一个映射,判断是否纯在并且打印出来

1
2
3
4
5
6
7
colore := map [string]string{}
colore["red"] = "#da1337"

value,exists :=colore["red"]
if exists{
fmt.Print(value)
}

删除

可以用delete删除一个键值对

1
delete(colore,"red")

在函数间传递映射

在函数间传递映射和切片类似,都是传递的内存地址。不会另外创建内存副本。

《深入理解C指针》

指针的讲解比较细致,面面俱到。很喜欢这本书,通俗易懂。结合这本书做了一些笔记 指针。 因为后续公司需要我掌握go的相关知识,5章以后的章节潦草看过未做笔记。

深入理解C指针

总的来说推荐这本书,写的很不错。

《嗨翻C语言》

不夸大不胡扯,C语言是我认为最值得去学习的一门语言。或许它不会在你薪资上给你什么直接的反馈,或许在一些二线城市压根就没有就业的机会,但是它是除汇编以外最接近计算机原理的语言了。是内功的一种修炼,我们理解了堆栈指针数据类型内存操作后发现,其实市面上很多语言都是基于这些展开的。

嗨翻C语言

看完这本书自己做了一系列的读书笔记
C自学

这本书是入门级书籍,主要分4章图文并茂

  • 第一章
    入门介绍历史背景等和一些流程语句

  • 第二章
    指针

  • 第三章
    C语言的类,方法。面向对象

  • 第四章
    编译

  • 第五章
    结构联合

  • 第六章
    堆栈操作,内存的动态分配

  • 第七章
    面向对象加深讲解

八章后主要讲解了一些标准库,进程和系统调用网络通信,线程等内容。八章后的知识点我选择性的快速阅读带过了。一是本书作为入门性数据对此方面讲解不够细致,一是因为有更好的数据可以代替后续内容,所以只是当平常阅读快速读完理解。

指针的奇技淫巧四_指针和数组

概述

数组是能用索引访问的同质元素的联系集合,这里的连续是指数组元素在内存中是相邻的,中间不存在空隙,而同质是指的相同的数据类型,数组声明用的[],并且可以是多维数组。

#一维数组
一维数组为线性结构,用一个索引访问成员,

1
2
//声明一个5个元素的数组,
int test[5];

注意:

  • 数组索引0开始 n-1结束
  • c语言没有强制规定边界,超出声明范围内的使用会有不可预期的行为
  • 提前声明数组类型是为低层索引偏移做准备
  • 数组size除去单个元素size可以得到元素的个数

#二维数组

二维数组在内存中是以列顺序实现的,先放入第一行接着第二行。。。。直到最后,二维数值也可以理解成数组的数组

1
2
//声明一个二维数值
int test[2][3] {{1,2,3},{4,5,6}};

#多维数组

多维数组具有两个及以上的维度。下面我们定义一个3行2列4阶的三维数组,阶通常用来标识第三维元素

1
2
3
4
5
int arr3d[3][2][4] = {
{{1,2,3,4},{5,6,7,8}},
{{9,10,11,12},{13,14,15,16}},
{{17,18,19,20},{21,22,23,24}}
}

#指针表示和数组

c语言里面指针处理数组是一个高效且节约开销的方法,我们可以用指针指向已有数组,也可以从堆上分配内存然后把这块内存当做一个数组使用。数组表示的方法和指针表示的方法并不完全相同,但是大多数情况下可以互换。

1
2
int vector[5] = {1,2,3,4,5};
int *p = vector;

我们可以只用数组名称,也可以对数组的第一个元素用取地址符操作一下代码等价

1
2
printf("%p\n",vector);
printf("%p\n",&vector[0]);
1
2
p[i];
*(p+i);

要点:

  • 单独使用数组名返回数组起始内存地址
  • p是指向数组第一个元素而不是指向数组本身的指针
  • 给p赋值是把数组的第一个元素给p,后续通过偏移修改其他元素
  • p[i]表示法其实就是偏移几位然后做解引操作
  • &vector获取数组地址

数组和指针的细微差别

命令 区别
vector[i] vector开始,偏移i个位置取出值
*(vector + i) vector开始,在地址上增加取出值
命令 区别
sizeof(vector) 返回20,数组分配的字节数
sizeof(p) 返回4,指针长度
命令 区别
p 左值可修改
vector 非左值不能修改

要点:

  • *(vector + i) 解引运算符比加优先级更高,所以需要强制加再解引

#用malloc创建一维数组

要点就是我们自己从堆上分配内存,把地址给一个指针然后对它进行地址偏移实现一个数组功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *array = (int*) malloc(sizeof(int)*5);//声明一个5列的数组指针

for (int i = 0; i < 5; ++i) {//给数组赋值
array[i] = i;
//*(array+i) = i; 这2种表示方法最后的结果相同
}

for (int j = 0; j < 5; ++j) {
printf("array[%d]:%d\n",j,array[j]);
}

return 0;
}

用malloc创建一维数组

要点:

  • 用完记住free掉

#用realloc调整数组长度

下面这个代码片段演示了一个动态数组分配的过程,书上所演示的实例。但是有些地方个人感觉有点问题。等有时间我会写一段自己能理解的内存动态分配的代码放这

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
char * getLine(void){
const size_t sizeIncrement = 10; //size_t表示无符号int unsigned int
char* buffer = malloc(sizeIncrement);//指向读入字符的指针
char* currentPosition = buffer;//指向缓存区中下一个空白位置的指针
size_t maximumLength = sizeIncrement;//可以安全的存入缓冲区的最大字符数
size_t length = 0;//读入字符数
int character;//上次读入的字符数

if(currentPosition == NULL){//检查指针分配情况
return NULL;
}

while (1){
character = fgetc(stdin);

if(character == '\n'){ //换行跳出循环
break;
}

if(++length >= maximumLength) {
char *newBuffer = realloc(buffer,maximumLength+=sizeIncrement);//过载动态添加内存

if(newBuffer == NULL) {//检查指针分配情况
free(buffer);
return NULL;
}

currentPosition = newBuffer + (currentPosition - buffer);//?
buffer = newBuffer;//?
}
*currentPosition = character;//?
}

*currentPosition = '\0';

return buffer;

}

下面这个例子是用c语言实现一个trim函数,动态减少堆分配空间。这个例子就比较理性而且可以正常跑通。trim函数中声明了2个while循环,第一个while是偏移指针找到不是空格的起始位置,第二个while是从空格后的起始位置做偏移,赋值给str的起始位置。相当于整个字符串往前挪动。最后动态的realloc掉不需要的内存空间

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
* 去除开头的空格
* @param str
* @return
*/
char *trim(char* str){

char* oldStr = str;
char* newStr = str;

while (*oldStr == ' '){//不等于空隔开始作为起始位置
oldStr++;
}
while (*oldStr){//偏移一个字符串
*(newStr++) = *(oldStr++);
}
*newStr = 0;//结尾符

return (char*)realloc(str,strlen(newStr)+1);
}

int main(){
char* str = (char*)malloc(strlen(" abc")+1);
strcpy(str," abc");
printf("%s\n",trim(str));
return 0;
}

realloc运行结果
#传递一维数组
待补全
#用数组表示方法
待补全
#用指针表示法
待补全
#使用指针的一维数组
待补全
#指针和多维数组
待补全
#传递多维数组
待补全
#动态分配二维数组
待补全
#分配连续内存
待补全
#不规则数组和指针
待补全

#注意

  • 数组和指针是完全可以互换是错误观点

指针的奇技淫巧三_指针和函数

程序的堆栈

堆和栈的区别其实不用过于的去强调他们了,我感觉理解下概念就好,比如程序哪些操作会在堆里,哪些会分配到栈里,它们实际使用中的差别什么的就好了

堆区:程序员自己申请,声明大小,释放
栈区:系统自动分配,自动释放

通过指针传递和返回数据

作为一个phper来说function的返回值直接一个return就解决问题了,但是C语言的函数返回就多了一些讲究和细节在里面。

用指针传递数据

用指针传递数据的根本原因是为了让函数可以修改数据,并二次调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

void swap(int* p1, int *p2){
int tmp;
tmp = *p1;
*p1 = *p2;
*p2 = tmp;

}

int main(){

int n1 = 100;
int n2 = 200;

swap(&n1, &n2);
printf("n1:%d\nn2:%d\n",n1,n2);
}

执行结果如下

用指针传递数据

swap函数通过指针操作内存地址的特性,修改了n1,n2内存地址在栈上保存的数据

指针交换内容演示

用值传递内容

这种方式是一种错误的演示方式,对新手有一定的迷惑性。我们要明白swap中的n1,n2只是形参,作用域也仅仅是在swap内,main中的n1,n2是实参他们没有半毛钱的关系哦!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>

void swap(int n1, int n2){
int tmp;
tmp = n1;
n1 = n2;
n2 = tmp;

printf("n1:%d\nn2:%d\n",n1,n2);
printf("\n------\n");
}

int main(){

int n1 = 100;
int n2 = 200;

swap(n1, n2);
printf("n1:%d\nn2:%d\n",n1,n2);
return 0;
}

内存分配如下图所示

用值传递内容

传递指向常量的指针

传递指向常量的指针是C语言中常用的套路,因为它效率很高,我们只传了数据的地址,能避免某些情况下的复制大量内存,并且常量指针可以避免传递的数据被修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

void increment(const int* n1, int* n2){

*n2 = *n1 + 1;

}

int main(){

int n1 = 100;
int n2 = 0;

increment(&n1, &n2);
printf("n1:%d\nn2:%d\n",n1,n2);
return 0;
}

输出结果如下

常量指针正确输出结果

如果我们尝试去修改这个常量会发生什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

void increment(const int* n1, int* n2){

*n1 = *n1 + 1;//这里尝试修改

}

int main(){

int n1 = 100;
int n2 = 0;

increment(&n1, &n2);
printf("n1:%d\nn2:%d\n",n1,n2);
return 0;
}

当然是会直接报错哦~~

尝试修改常量的报错提示

返回指针

返回指针很容易,只要返回的类型是相同的数据类型即可,从函数返回对象经常用到下面两种技术

  • 使用malloc在函数内部分配内存,返回地址,调用者释放返回内存
  • 传递一个对象给函数并让函数修改它,这样分配内存和释放内存都是调用者负责
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
int* initArray(int size, int value){

int* array = (int*)malloc(sizeof(int) * size);

for (int i = 0; i < size; ++i) {
array[i] = value;
}

return array;
}

int main(){

int* array = initArray(5,45);

for (int i = 0; i < 5; ++i) {
printf("%d\n",array[i]);
}

free(array);
return 0;
}

执行结果
指针的指针

注意:在initArray 内部的实参在函数运行完成后都会被系统自动回收掉,但是我们malloc分配的内存是自己分配处理的。initArray返回的是内存地址

局部数据指针(错误的演示)

这个演示给只懂phper的程序员来说看不出任何问题,因为PHP就是这样完成数据传递的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
int* initArray(int size, int value){

int array[size]; //这块是使用错误的地方

for (int i = 0; i < size; ++i) {
array[i] = value;
}

return array;
}

int main(){

int* array = initArray(5,45);

for (int i = 0; i < 5; ++i) {
printf("%d\n",array[i]);
}

free(array);
return 0;
}

局部数据指针

在部分编译器中能通过,但是本地测试的时候是不通过的。大概的意思是initArray中的数据全部是在程序栈中完成的,一旦函数返回旧GG了,栈针全部弹出虽然空间返还给程序内存,但是其它代码可能会复用这一快区域造成不可预期的结果。有一种解决方法是把array设置为static这一可以把作用域限制在函数内部,栈针在函数外面,避免其它函数覆盖,但是这样做有很多问题还是不建议使用。

传递指针的指针

传递指针的指针时函数内部不需要返回值,但是需要定义一个二维指针,二维指针这要好好理解下。稍不注意就理解的没这彻底了。

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
#include <stdio.h>
#include <stdlib.h>

void initArray(int** array, int size, int value){


*array = (int*)malloc(sizeof(int)*size);//动态分配传入的空指针

if(*array != NULL){
for (int i = 0; i < size; ++i) {
//*array[i] = value; 这种写法是错误的,*array是传入array的内存地址
*(*array+i) = value;//给内存地址做偏移
}
}

}

int main(){

int* array = NULL;//初始化一个空指针
initArray(&array,5,45);

for (int i = 0; i < 5; ++i) {
printf("%d\n",array[i]);
}

free(array);
return 0;
}

指针的指针

需要注意的2点

  • initArray 传入的第一个参数是再外部定义好的空指针
  • 内存做偏移是 *(*array+i)

initArray 内部的指针讲解

*array main函数传入的 array的内存地址
*(*array) 第一段main传入的值
*(*array+i) main传入的array内存地址偏移i的内存存储值

函数指针

函数指针是持有函数地址的指针,类似于别名但是不是别用,有点像PHP里的依赖注入。通过类名称反射找到需要执行的类函数,但是这里不是,这里是调用函数的内存地址。

声明函数指针

void(*foo)();

void (*foo) ();
返回类型 函数指针变量名字 参数

我们对函数指针在命名约定上的建议是用fptr做前缀

命令 说明
int *f4(); f4是一个函数返回整数指针
int (*f5)(); f5是一个返回整数的函数指针
int* (*f6)(); f6是一个返回整数指针的函数指针

使用函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>

int square(int num){//定义一个函数方法
return num;
}

int main(){
int (*fptr1)(int);//返回整数的函数指针
fptr1 = square;//fptr1 = &square
int num = 1;
printf("%d\n", fptr1(num));
return 0;
}

传递函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>

int add(int num1, int num2){
return num1 + num2;
}

int sub(int num1, int num2){
return num1 - num2;
}

typedef int(*fptrOperation)(int,int);

int compute(fptrOperation operation, int num1, int num2){
return operation(num1, num2);
}

int main(){
printf("%d\n",compute(sub,5,6));
}

返回函数指针

返回指针需要把函数的返回类型声明为函数指针

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
#include<stdio.h>

int add(int num1, int num2){
return num1 + num2;
}

int sub(int num1, int num2){
return num1 - num2;
}

typedef int(*fptrOperation)(int,int);


fptrOperation select(char opcode){//返回格式定义为自定义函数指针

switch (opcode){
case '+':
return add;
case '-':
return sub;
default:
return add;
}
}

int compute(char opcode, int num1, int num2){
fptrOperation function = select('-');//类似工厂模式
return function(num1, num2);
}


int main(){
printf("%d\n",compute('+',5,6));
}

##函数指针数组

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
#include<stdio.h>


int add(int num1, int num2){
return num1 + num2;
}

int sub(int num1, int num2){
return num1 - num2;
}

typedef int(*Operation)(int,int);


Operation function[66] = {NULL};//函数指针数组

void setFunction(){

function['+'] = add;//这里的add是函数的指针地址
function['-'] = sub;
}

int compute(char opcode, int num1, int num2){
Operation functionName = function[opcode];
return functionName(num1, num2);
}


int main(){
setFunction();
printf("%d\n",compute('+',5,6));
}

查看服务器资源占用

上上周的时候请教了运维大佬几个常用的查看服务器使用资源状况的命令,在服务器上观察监控状态一般是使用的数据看板,本地MAC下就用了一个top和htop。比较单一,贴出了这3个常用命令并且解释了一下。

#虚拟内存统计 vmstat

1
2
3
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 402468 41120 649276 0 0 1 17 1 0 1 1 99 0 0

Procs(进程)

r: 运行队列中进程数量,这个值也可以判断是否需要增加CPU。(长期大于1)
b 等待IO的进程数量。

Memory(内存)

swpd 使用虚拟内存大小,如果swpd的值不为0,但是SI,SO的值长期为0,这种情况不会影响系统性能。
free 空闲物理内存大小。
buff 用作缓冲的内存大小。
cache 用作缓存的内存大小,如果cache的值大的时候,说明cache处的文件数多,如果频繁访问到的文件都能被cache处,那么磁盘的读IO bi会非常小。

Swap

si 每秒从交换区写到内存的大小,由磁盘调入内存。
so 每秒写入交换区的内存大小,由内存调入磁盘。

注意:内存够用的时候,这2个值都是0,如果这2个值长期大于0时,系统性能会受到影响,磁盘IO和CPU资源都会被消耗。有些朋友看到空闲内存(free)很少的或接近于0时,就认为内存不够用了,不能光看这一点,还要结合si和so,如果free很少,但是si和so也很少(大多时候是0),那么不用担心,系统性能这时不会受到影响的。因为linux总是先把内存用光

IO

bi 每秒读取的块数
bo 每秒写入的块数

注意:随机磁盘读写的时候,这2个值越大(如超出1024k),能看到CPU在IO等待的值也会越大。

system(系统)

in 每秒中断数,包括时钟中断。
cs 每秒上下文切换数。

CPU(以百分比表示)

us 用户进程执行时间百分比(user time) us的值比较高时,说明用户进程消耗的CPU时间多,但是如果长期超50%的使用,那么我们就该考虑优化程序算法或者进行加速。
sy: 内核系统进程执行时间百分比(system time) sy的值高时,说明系统内核消耗的CPU资源多,这并不是良性表现,我们应该检查原因。
wa IO等待时间百分比 wa的值高时,说明IO等待比较严重,这可能由于磁盘大量作随机访问造成,也有可能磁盘出现瓶颈(块操作)。
id 空闲时间百分比

网速 iftop

界面上面显示的是类似刻度尺的刻度范围,为显示流量图形的长条作标尺用的。
中间的<= =>这两个左右箭头,表示的是流量的方向。

名称 介绍
TX 发送流量
RX 接收流量
TOTAL 总流量
Cumm 运行iftop到目前时间的总流量
peak 流量峰值
rates 分别表示过去 2s 10s 40s 的平均流量

httpstat

一目了然就不解释了

指针的奇技淫巧二__C的动态内存管理

指针的强大很大程度上源于它能追踪动态分配的内存,内存管理对所有的程序来说都是相当重要的,有程序运行时内存的隐式管理,也有我们下面讲的自己把数据推到内存堆上管理。PHP对于这一点额。。。

#动态内存分配
在C中动态分配内存的基本步骤有:

  1. 用malloc类的函数分配内存;
  2. 用这些内存支持应用程序;
  3. 用free函数释放内存。
1
2
3
4
int *pi = (int*) malloc(sizeof(int));
*pi = 5;
printf("*pi: %d\n",*pi);
free(pi)

解引操作常见的错误

1
2
int *pi;
*pi = (int*) malloc(sizeof(int));

问题出在赋值符号左边。我们再解引指针,这样会把malloc函数返回的地址赋值给pi中存放的地址所在的内存单元。通俗理解就是修改了pi指向的值,但是未修改pi执行的地址的值。有点难理解。可以产考下个人对指针书写的理解。
认识指针

正确的方法如下

1
2
pi = (int*) malloc(sizeof(int));

解释下代码意识,方便记忆

错误的方法

*pi = (int*) malloc(sizeof(int));
pi指向的内存地址的值 强转为int 声明一个int单位堆内存

正确的方法

pi = (int*) malloc(sizeof(int));
pi指向的内存地址的值 强转为int 声明一个int单位堆内存

ps:但是我们可以这样赋值

1
int *pi = (int*) malloc(sizeof(int));

在编译器看来,作为初始化操作符的=和作为赋值操作符的=不一样!!


内存泄露

如果不再使用已分配的内存却没有将其释放就会发生内存泄露,导致内存泄露的主要情况可能如下

  • 丢失内存地址
  • 应该调用free函数却没有调用(隐式泄露)

丢失内存地址举例

1
2
3
4
5
int *pi = (int*) malloc(sizeof(int));
*pi = 5;
...
pi = (int*) malloc(sizeof(int));

在第一次malloc申请一个堆的时候,操作系统分配了一个内存地址给我们,并且赋值给了指针pi,我们往这个地址中写入了数据(int 5)。后续申请一个堆,覆盖了之前申请的地址,指向5的内存地址的值就丢失了,但是int 5 占用的内存空间并没有释放掉。

动态内存分配函数

这里要介绍几个可以用来管理动态内存的函数

名称 作用
malloc 从堆上分配内存
realloc 在之前分配的内存块基础上,将内存重新分配更大或者更小
calloc 从堆上分配内存并清零
free 将内存块返回堆

malloc

从堆上分配一块内存,所分配的字节数由该函数唯一的参数指定,返回值是void指针,如果内存不足返回NULL,此函数不会清空或者修改内存,所以我们认为新分配的内存包含垃圾数据函数原型如下:

1
void* malloc(size_t);

执行malloc函数会进行以下操作:

  1. 从堆上分配内存
  2. 内存不会被修改或者清空
  3. 返回首字节的地址

注:当malloc无法分配内存时会返回NULL,在使用它返回指针之前先检查NULL是个好的习惯

1
2
3
4
5
6
int *pi = (int*) malloc(sizeof(int));
if(pi != NULL){
//指针没问题
}else{
//无效的指针
}

场景一: 要不要强制类型转换?

在两种不相互不兼容的指针类型之间赋值需要对malloc使用显式转换类型,避免警告。如果是赋值给void指针就不需要显式转换了,但是个人觉得malloc后强制类型转换是一个比较好的编码习惯,因为

  • 这样可以说明malloc函数的用意;
  • 可以兼容C++和早期的C编译器

场景二:分配内存失败

如果声明一个指针,但是没有在使用它之前为它指向的地址分配内存,那么内存通常会包含垃圾,这样会导致一个无效内存引用的错误

1
2
3
int *pi;
...
printf("%d\n",*pi);

字符串操作这类问题比较常见

1
2
3
char *name;
printf("enter a name:");
scanf("%s",name);

这看起来貌似没什么问题,天衣无缝。但是实际上name的这块内存还没有分配,也就是指针没有指向任何的内存地址。这容易和 char name;声明一个字符变量搞混。


场景三:没有给malloc传递正确的参数

  • 确认可分配的最大内存数
  • malloc传递不能为负数

场景四:静态,全局指针和malloc

初始化静态或全局变量时不能调用函数,这样会编译时会产生一个错误信息。列如

1
static int *pi = malloc(sizeof(int));

对于静态变量,可以通过在后面用一个单独的语句给变量分配内存来避免这个问题。

1
2
static int *pi;
pi = malloc(sizeof(int));

calloc

calloc 会在分配的同时清空内存,函数原型如下:

1
void *calloc(size_t numElements, size_t elementSize);

清空内存是指将内容设置为二进制0
下列为pi分配20字节,全包含0:

1
int *pi = calloc(5,sizeof(int));

使用malloc这样实现

1
2
int *pi = malloc(5 * sizeof(int));
memset(pi,0,5*sizeof(int));

memset函数会用某个值填充内存块,第一个参数是指向要填充缓冲区的指针,第二个参数是填充缓冲区的值,最后一个是要填充的字节数

realloc

我们会时不时的增加或者减少指针分配给我们的内存,毕竟程序变换和产品爸爸的心情一样的嘛~如果是动态的变长数组这个方法尤为有用。

1
void *realloc(void *ptr, size_t size);
名称 介绍
*ptr 指向原内存块的指针
size 请求大小
void * 返回指向重新分配的内存指针

如果请求的比当前的分配小,那么多余的内存会返还给堆,不能保证多余的内存会清空。如果请求的比当前的大,那么就尽量分配紧挨着当前分配内存,如果没有就在堆上其他区域分配把当前的内存复制到新的区域去

realloc函数行为

第一个参数 第二个参数 行为
同malloc
非空 0 原内存块被释放
非空 比原内存块小 利用当前块分配更小的块
非空 比原内存块大 要么当前位置要么其他位置分配跟大的快

realloc 在变小了指针内存过后溢出的内存不会马上释放,但是我们也修改不了了

free

函数原型

1
void free(void *ptr);

特别需要明白的概念是,free释放掉内存后,我们的指针还是指向的这块malloc函数分配的内存地址,这块内存被返回给了堆,尽管指针依然指向这块区域,但是我们应该将它看成指向了垃圾数据。这块区域稍后可能会被重新的分配写入不可预期的内容。

需要注意的几点:

  • 将已释放的指针赋值为NULL是一个好习惯
  • 重复释放运行会导致堆损坏和终止程序,同一块内存我们没有理由释放两次
  • 释放的内存会返回给堆供应用程序的后续使用,free不会体现在程序的内存使用上

迷途指针

迷途指针上面在注意点一有说到,将已释放的指针赋值为NULL可以避免掉。迷途指针是指:内存已经释放,而指针还在引用原始内存。

使用迷途指针会造成一系列的麻烦:

  • 如果访问内存,则行为不可预期;
  • 如果内存不可访问,则是段错误;
  • 潜在的安全隐患

指针的奇技淫巧一__认识指针

指针是一个C语言中比较重要的概念,C语言对内容管理的技巧也逃不掉指针的概念。这边我结合《深入理解C指针》这本书做了一些笔记。

为什么要精通指针

  • 写出快速高效的代码
  • 为解决很多类问题提供方便的途径
  • 支持动态分配内存
  • 使表达式更加的紧凑和简洁
  • 提供用指针传递数据结构的能力而不会带来庞大的开销
  • 保护作为参数传递给函数的数据

声明指针

通过在数据后面跟星号,再加上指针变量名字就可以声明一个指针

1
2
int num;
int *pi

星号两边的空白符无关紧要,下面的声明等价

1
2
3
4
int* pi
int * pi
int *pi
int*pi

*将变量声明为指针,这是一个重载过的符号,它也用在乘法和解引指针上

如何阅读声明

阅读一个指针推荐的阅读方式是重后往前读。

理解 声明
pci是一个变量 const int *pci
pci是一个指针变量 const int *pci
pci是一个指向整数的指针变量 const int *pci
pci是一个指向整数常量的指针变量 const int *pci

地址操作符

地址操作符&会返回操作数的地址。我们可以用这个操作符来初始化pi指针

1
2
3
num = 0;
pi = &num;

可以在声明变量pi的同时把它初始化为num地址

1
2
int num;
int *pi = &num;

尽快的初始化指针是一个好习惯

1
int *pi = null;

打印指针的值

1
2
3
4
int num = 0;
int *pi = &num;
printf("Address of num :%d value:%d \n",&num,num);
printf("Address of pi :%d value:%d \n",&pi,num);

输出

这里输出的真实地址用:地址A,地址B表示

1
2
Address of num :地址A value :0
Address of pi :地址B value :地址A

用间接引用操作符解引指针

(*)一般称为解引指针,我们可以把解引操作符的结果用作左值,术语左值是指赋值操作符左边的操作数所以有左值都必须可以修改,因为它会被赋值

总结

  1. 指针的值是指针指向的地址
  2. &pi,是指针的地址
  3. *pi,是指针指向变量的内容,俗称解引

请我喝杯咖啡吧~