Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

我记不住的那些编程语言的语法(数组)-1

背景:我记不住各种语言的语法,例如C、Java、Go、Python、JavaScript,大概就是常用的这几种语言,每种语言有其自己的语法规范,有的时候会记混了,所以想记录一下细节。这个系列会不定期的更新,本期是各类语言的数组。

一、C语言的数组

C语言的数组是非常重要的一种数据结构,下面是关于C语言数组的各种使用语法。

如果引用不存在的数组成员(即越界访问数组),并不会报错,所以必须非常小心。

/*
 *  C 语言数组
*/
int scores[100];

// 越界访问,不会报错,需要注意
scores[100] = 51;

// 可以用大括号赋值。
int a[5] = {22, 37, 3490, 18, 95};

// b[3]、b[4] 默认初始化为0
int b[5] = {32,58,123};

// 同时可以为指定位置的变量设置值,其余初始化为0
int c[15] = {[2] = 29, [9] = 7, [14] = 48};

// 省略数组的大小,由初始化的值自动计算大小,这时d的大小是3
int d[] = {22, 37, 3490};


// 数组的所占据的字节数,得出12个字节
int e[] = {22, 37, 3490};
int arrLen = sizeof(e); // 12


// 数组中的成员数目
sizeof(e)/sizeof(e[0])

// 多维数组
int board[10][10];

// 初始化
int a[2][5] = {
  {0, 1, 2, 3, 4},
  {5, 6, 7, 8, 9}
};

int a[2][5] = {0,1,2,3,4,5,6,7,8,9};

// 如果对全部元素赋初值,可以省略第一维的长度,但第二维不能省略
int a[][5] = {0,1,2,3,4,5,6,7,8,9};

int a[3][4] = {{1},{5},{9}};

[1  0  0
 5  0  0
 9  0  0]

int a[3][4] = {{1},{0,6},{0,0,11}};
[ 1 0 0 0
  0 6 0 0
  0 0 11 0]


// 指定位置进行初始化
int a[2][2] = {[0][0] = 1, [1][1] = 2};

// 数组均是线性存储,也可以用一个大括号进行赋值操作
int a[2][2] = {1, 0, 0, 2};

// 变长数组,也就是数组大小不定,是一个变量,无法在编译期间确定,只在运行时候确定。
int i = 10;

int a1[i];
int a2[i + 5];

int m = 4;
int n = 5;
int c[m][n];

// 数组指针
// 数组名是一个指针,指向第一个元素的地址
int a[5] = {11, 22, 33, 44, 55};

int* p = &a[0];
// 等同于
int* p = a;

// a 与 &a[0] 是等价的, a是&a[0]的一种简写

// 可以将一个数组指针作为函数参数,传入到函数内部,在函数内部可以通过数组指针进行操作整个数组
// 这两种写法是一样的
// 写法一
int sum(int arr[], int len);
// 写法二
int sum(int* arr, int len);

// 二维数组 相当于 指针的指针
int a[4][2];

// 由于a[0]本身是一个指针,指向第二维数组的第一个成员a[0][0]。所以,*(a[0])取出的是a[0][0]的值。至于**a,就是对a进行两次*运算,第一次取出的是a[0],第二次取出的是a[0][0]。

// 取出 a[0][0] 的值

a[0]  等同于 *(a+0)

*(a[0]) 等同于 *(*(a+0))
// 也就等同于
**a

// 通过指针的移动遍历数组,a + i的每轮循环每次都会指向下一个成员的地址,
// *(a + i)取出该地址的值,等同于a[i] 
// 对于数组的第一个成员, *(a + 0)(即*a)等同于a[0]

// 公式是  a[b] == *(a + b)

*p++  是什么?

*和++属于同一个优先级,但是 结合方向是 自右向左,因此等价于 *(p++) 即先得到p所指向变量的值,
再将p自增。



// 如果指针变量p指向数组的一个成员,那么p++就相当于指向下一个成员,这种方法常用来遍历数组
// 但容易出现数组越界的问题

int a[] = {11, 22, 33, 44, 55, 999};

int* p = a;

while (*p != 999) {
  printf("%d\n", *p);
  p++;
}

// 遍历数组一般都是通过数组长度的比较来实现,但也可以通过数组起始地址和结束地址的比较来实现
int sum(int* start, int* end) {
  int total = 0;
  while (start < end) {
    total += *start;
    start++;
  }
  return total;
}

int arr[5] = {20, 10, 5, 39, 4};
printf("%i\n", sum(arr, arr + 5));

// 二维数组指针的加减
int arr[4][2];

// arr 是二维数组名,指向一维数组 arr[0],即 0行首地址
所以 arr是一个  以 一行的列数为 offset 的指针

// 当 arr + 1 , 指针指向 arr[1] 即 1行首地址 
arr + 1;

//  二维数组 arr+1  和 *(arr+1) 的相同和不同

arr+1 指向 1行的首地址,offset为 一行的数据,即 arr为 行指针

*(arr+1) 指向 1行的0列的首地址, offset为 一个元素的数据, 

等同于  *(arr+1) + 0 即  *(arr+1) + 0 为 列指针

虽然地址都相同,但是 他们的offset不同。

// 指针指向 arr[0][1]
arr[0] + 1

arr[i][j] 等同于  *(*(arr+i)+j)   也等同于 *(arr[i]+j)

二维数组的指针作为函数参数
有两种方式:
第一种是: 传单个元素的类型,和总数量,即 average函数
第二种是: 传行元素的类型,和第几个, 即search函数 

float score[3][4] = {65,67,70,60,80,87,90,81,90,99,100,98};
void average(float* p, int n);
void search( float (*p)[4],int n);
average(*score,12);
search(score,2);

void average(float* p, int n){
    float* end;
    float sum=0,aver;
    end = p+n-1;
    for(;p<=end;p++){
        sum+= (*p);
    }
    aver = sum/n;
    printf("average=%5.2f\n",aver);
}

void search(float (*p)[4],int n){
    printf("the score of No. %d are: \n",n);
    for(int i=0;i<4;i++){
        printf("%5.2f",*(*(p+n)+i))
    }
}

也就是说,要想使用 *(*(arr+i)+j) 即 arr[i][j],此时的arr是一个行指针变量

// 数组的复制
// 第一种方式是 循环进行赋值
// 第二种方式是 使用memcpy()函数

void *memcpy(void *dest, const void *src, size_t n)
// 将src的n个字节复制到dest中

char str1[] = "Geeks"; 
char str2[] = "Quiz"; 
 
puts("str1 before memcpy ");
puts(str1);
 
/* Copies contents of str2 to str1 */
memcpy (str1, str2, sizeof(str2));
 
puts("\nstr1 after memcpy ");
puts(str1);

// 数组作为函数的参数传入函数内部
int sum_array(int a[], int n) {
  // ...
}
int a[] = {3, 5, 7, 3};
int sum = sum_array(a, 4);


// 变长数组作为函数的参数,先写元素个数,再写元素地址
// 变量n作为参数时,顺序一定要在变长数组前面,这样运行时才能确定数组a[n]的长度,否则就会报错
int sum_array(int n, int a[n]) {
  // ...
}
int a[] = {3, 5, 7, 3};
int sum = sum_array(4, a);

//函数原型可以省略参数名,所以变长数组的原型中,可以使用*代替变量名,也可以省略变量名。
int sum_array(int, int [*]);
int sum_array(int, int []);

// 变长数组的写法
int sum_array(int n, int m, int a[n][m]);

// 还可以使用 数组字面量作为参数
int sum = sum_array((int []){2, 3, 4, 5}, 4);


// 字符串数组
// 在C语言中,字符串是一个字符型数组,而字符串数组是一个二维的字符型数组,每一个字符串由\0结尾
// 语法为:
// char var_name[r][c] = {list of string};

// var_name是一个变量名
// r是字符串的个数的最大值, c是每个字符串存储的最大的字符数

char arr[3][10] = {"Geek","Geeks", "Geekfor"};
3代表3个字符串,10代表每个字符串的最大字符数。

关于字符串数组,

char arr[3][10] = {"Geek","Geeks", "Geekfor"};

会导致浪费内存空间,并且无法修改,

arr[0] = "china"  // 这样会报错,因为arr[0]指向的是一个地址, 

但是可以通过

strcpy(arr[0],"china")  // 这样将会把字符串"china"拷贝到arr[0]上
为了节省空间,我们可以使用指针数组,是一个一维的数组,里面存储的是指针

优点: 1. 可以任意修改各个字符串    2. 还不会浪费内存空间

char* arr[] = {"Geek","Geeks","Geekfor"};

arr[1] = "china";   // 这样可以修改

二、Java语言的数组

Java语言的数组和C语言数组语法不一样,但都是指针,只不过在Java,都是通过指向内存地址来进行访问,这里记录一下。

// 数组类型 : “类型[]” 这是一种类型
// 数组在初始化的时候必须使用 new 类型[n]来进行初始化
// Java的数组有几个特点:

// 第一: 数组所有元素初始化为默认值,整型都是0,浮点型是0.0,布尔型是false;
// 第二: 数组一旦创建后,大小就不可改变。
// 第三: 要访问数组中的某一个元素,需要使用索引。数组索引从0开始,
// 第四: 数组如果索引超出范围会报错,而C语言不会报错,这是Java和C语言的不同

int[] ns = new int[5];

// 数组长度通过数组的length属性来获取
ns.length

// 定义数组时直接指定初始化的元素,这样就不必写出数组大小,而是由编译器自动推算数组大小,和C语言是一致的。

int[] ns = new int[] { 68, 79, 91, 85, 62 };
或者 简写为
int[] ns = { 68, 79, 91, 85, 62 };   // 与C语言相似,只是数组类型的写法不一样。

for(int i = 0;i< ns.length;i++){
    System.out.println(ns[i]);
}

for(int n: ns){
    System.out.println(n);
}

// Java数组也是引用类型,可以理解为和C语言是一致的
// 字符串数组
String[] names = {"ABC", "XYZ", "zoo"};
String[] s = names;
names[1] = "cat";
System.out.println(names[1]);    // cat
System.out.println(s[1]);        // cat

// "ABC"、"XYZ"和"zoo"是三个字符串变量,存在于内存中的不同的地址
// names[0]是一个字符串指针,指向了内存中”ABC“的地址
// names[1]是一个字符串指针,指向了内存中"XYZ"的地址
// names[2]是一个字符串指针,指向了内存中"zoo"的地址
// names也是一个指针指向初始的地址,即指向 names[0]
// 所以在names数组中 是存放各个字符串的指针,与C语言的指针数组是一样的
// 指针数组: 存放若干指针的数组。

// 当修改某个字符串时候,其实是改变了指针的指向。
names[1] = "cat";   // 首先在内存中新建一个"cat",然后将names[1]指向"cat"的地址

三、Go、

    //数组复制
    fmt.Println("数组复制赋值")
    arr_a:=[5]int{1,2,3,4,5}
    var arr_b [5]int
    arr_b=arr_a
    fmt.Println("赋值前:")
    fmt.Println(arr_a)
    fmt.Println(arr_b)

    arr_b[0]=100
    fmt.Println("赋值后:")
    fmt.Println(arr_a)
    fmt.Println(arr_b)
    fmt.Println("数组赋值是值赋值,两个是分离的")

package main

import "fmt"

func printArray(arr [5]int)  {
	for i:=range arr{
		fmt.Println(arr[i])
	}
	arr[0]=100
}

func printArray2(arr *[5]int) {
	arr[0] = 100
	for i := range arr {
		fmt.Println(arr[i])
	}
}

func updateSlice(s []int)  {
	s[0]=100
}

func printArray3(arr []int) {
	arr[0] = 100
	for i := range arr {
		fmt.Println(arr[i])
	}
}

func printSlice(s []int) {
	fmt.Printf("value=%v,len=%d,cap=%d\n",s,len(s),cap(s))
}

func main() {

	var arr [5]int
	arr2:=[5]int{2,3,4,5,6}
	arr3:=[...]int{3,4,5,6,7}
	var arr_text [5]int=[5]int{3,4,5,6,7}
	fmt.Println(arr_text)

	var grid [4][5]int
	fmt.Println(arr,arr2,arr3)
	fmt.Println(grid)
	for i:=0;i<len(arr3);i++{
		fmt.Println(i,arr3[i])
	}
	fmt.Println()
	//只要下标
	for i:=range arr3{
		fmt.Println(i)
	}
	fmt.Println()
	//两个都要
	for i,v:=range arr3{
		fmt.Println(i,v)
	}
	//只要值
	for _,v:=range arr3{
		fmt.Println(v)
	}

	//为什么要使用range
	//数组是值类型
	printArray(arr2)
	fmt.Println(arr2)

	//[10]int和[20]int是不同类型
	//调用func f(arr [10]int)会拷贝数组
	//可以用指针改变数组值
	printArray2(&arr2)
	fmt.Println(arr2)

	//可以使用slice改变数组值
	arr2=[5]int{2,3,4,5,6}
	printArray3(arr2[:])
	fmt.Println("slice:",arr2)



	//数组复制
	fmt.Println("数组复制赋值")
	arr_a:=[5]int{1,2,3,4,5}
	var arr_b [5]int
	arr_b=arr_a
	fmt.Println("赋值前:")
	fmt.Println(arr_a)
	fmt.Println(arr_b)

	arr_b[0]=100
	fmt.Println("赋值后:")
	fmt.Println(arr_a)
	fmt.Println(arr_b)
	fmt.Println("数组赋值是值赋值,两个是分离的")


	//go语言一般不直接使用数组,可以使用切片slice
	//下面是依据数组构建出slice
	arr4:=[...]int{0,1,2,3,4,5,6,7}
	fmt.Println("arr4[2:6] =",arr4[2:6])
	fmt.Println("arr4[:6] =",arr4[:6])
	fmt.Println("arr4[2:] =",arr4[2:])
	fmt.Println("arr4[:] =",arr4[:])

	//slice是对arr的一个view视图
	//更改slice也会对底层的arr进行更改,原始数据被更改
	s1:=arr4[2:]
	s2:=arr4[:]					//共享底层数组
	fmt.Println("s1=",s1)
	fmt.Println("s2=",s2)
	fmt.Println("after updateSlice s1")
	updateSlice(s1)
	fmt.Println("s1=",s1)
	fmt.Println("s2=",s2)
	fmt.Println("arr4=",arr4)

	//slice本身没有数据,是对底层array的一个视图view

	myArray:=[...]int{10,11,12,13,14,15,16,17}
	s:=myArray[2:6]
	s[0]=100
	fmt.Println("after slice s:",s)
	fmt.Println("after slice array:",myArray)

	//进一步的去slice
	aa:=s[2:]
	fmt.Println(aa)
	bb:=s[:6]
	fmt.Println(bb)

	//扩展的slice,其实就是 切片的切割,从切片中再取一部分东西成为子切片,子切片和母切片共享底层数组
	fmt.Println("Extending slice")
	myArray2:=[...]int{0,1,2,3,4,5,6,7}
	s11:=myArray2[2:6]
	s21:=s11[3:5]
	fmt.Println("子切片和母切片")
	fmt.Println(myArray2,s11,s21)
	//但是单取s11[4]是取不出来的
	//slice可以向后扩展,不可以向前扩展
	//s11[4]不可以超越len(s11),向后扩展不可以超越底层数组cap(s11)
	fmt.Println(len(s11),cap(s11))
	
	fmt.Printf("s11=%v,len(s11)=%d,cap(s11)=%d\n",s11,len(s11),cap(s11))
	fmt.Printf("s21=%v,len(s21)=%d,cap(s21)=%d\n",s21,len(s21),cap(s21))

	//向slice添加元素,append会添加元素,并且生成一个新的切片
	/*
	正是因为切片追加后是新的切片变量,
	Go 编译器禁止追加了切片后不使用这个新的切片变量,
	以避免用户以为追加操作的返回值和原切片变量是同一个变量

	*/

	// s34,s35 no longer view arr,添加元素如果超越cap,系统会重新分配更大的的底层数组
	// 由于值传递的关系,必须接收append的返回值
	myArray3:=[...]int{0,1,2,3,4,5,6,7}
	s31:=myArray3[2:6]
	s32:=s31[3:5]
	s33:=append(s32,10)
	s34:=append(s33,11)
	s35:=append(s34,12)
	fmt.Println(s31,s32,s33,s34,s35,myArray3)

	//如果不使用这个切片,可以将其赋值给 _
	_ = append(s35,10)




	//如何创建一个slice??? 前面都是先是创建数组,然后view一个slice。
	//
	fmt.Println("creating slice")
	var ss []int			//这是一个slice,但是下面是没有数组可以view的
	// zero value for slice is nil,ss==nil
	for i:=0;i<100;i++{
		printSlice(ss)
		ss=append(ss,2*i+1)
	}
	fmt.Println(ss)

	ss1:=[]int{2,4,6,8}
	printSlice(ss1)


	//最通用的方式是使用内置的make函数创建切片。
	//make传递三个参数,切片类型,长度,容量。第三个参数容量可选(默认等于长度)
	//创建的切片是 "零值切片"
	ss2:=make([]int,16)
	ss3:=make([]int,10,32)
	printSlice(ss2)
	printSlice(ss3)
	//还有一种方式创建切片,允许给它赋值,但这种切片创建的都是满容的
	//[]int是类型,也算是切片的一种表示,这个在后面{}中初始化
	var sss []int = []int{1,2,3,4,5}
	fmt.Println(sss)
	//还有一种称作:空切片,就是长度和容量均为0的切片,不同于零值切片
	var x1 []int=[]int{}
	var x2 []int=make([]int,0)

	//这是nil切片
	var xx []int
	fmt.Println(x1,x2,xx)

	//切片赋值,拷贝的是切片变量的三个域
	var sx=make([]int,5,8)
	for i:=0;i<len(sx);i++{
		sx[i]=i+1
	}
	//拷贝前后共享底层数组,对一个切片的修改会影响另一个切片的内容
	var sy []int =sx
	fmt.Println(sx,len(sx),cap(sx))
	fmt.Println(sy,len(sy),cap(sy))
	sy[0]=255
	fmt.Println(sx,len(sx),cap(sx))
	fmt.Println(sy,len(sy),cap(sy))


	//slice遍历

	for index:= range sx{
		fmt.Println(index)
	}
	for index,value:= range sx{
		fmt.Println(index,value)
	}
	for _,value:= range sx{
		fmt.Println(value)
	}


	/*
	切片每一次追加后都会形成新的切片变量,如果底层数组没有扩容,那么追加前后的两个切片变量共享底层数组,
	如果底层数组扩容了,那么追加前后的底层数组是分离的不共享的。如果底层数组是共享的,
	一个切片的内容变化就会影响到另一个切片,
	这点需要特别注意
	*/


	/*
	Go 语言还内置了一个 copy 函数,用来进行切片的深拷贝。不过其实也没那么深,
	只是深到底层的数组而已。如果数组里面装的是指针,
	比如 []*int 类型,那么指针指向的内容还是共享的。

	copy 函数不会因为原切片和目标切片的长度问题而额外分配底层数组的内存,
	它只负责拷贝数组的内容,从原切片拷贝到目标切片,拷贝的量是原切片和目标切片长度的较小值
	—— min(len(src), len(dst)),函数返回的是拷贝的实际长度。

	*/
	fmt.Println("copying slice")
	copy(ss2,ss1)
	printSlice(ss2)


	var sq1=make([]int,5,8)
	for i:=0;i<len(sq1);i++{
		sq1[i]=i+1
	}
	fmt.Println(sq1)
	var sq2=make([]int,2,6)
	var num=copy(sq2,sq1)
	fmt.Println(sq2,num)


	fmt.Println("deleting elements from slice")
	ss2=append(ss2[:3],ss2[4:]...)  		//删除中间值
	printSlice(ss2)

	fmt.Println("popping from front")
	front:=ss2[0]
	ss2=ss2[1:]
	fmt.Println(ss2,front)
	printSlice(ss2)

	fmt.Println("popping from back")
	tail:=ss2[len(ss2)-1]
	ss2=ss2[:len(ss2)-1]
	fmt.Println(ss2,tail)
	printSlice(ss2)


}

四、Python、JavaScript

关于Go/Python/JavaScript的相关知识,后续补充,敬请期待

添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Penguinbupt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值