目录
概述:
C语言的语法不能靠死记硬背,而是需要理解,真正理解了背后的原理,C数组的难点也就不攻自破了。特别需要重点深入理解甚至死记的知识点如下:
- 计算机内存被分成一个个的内存单元,每个单元大小为一个字节,给每个单元一个编号就是这个单元的地址,地址也被称为指针。所以是否可以重新赋值就涉及到地址是否可以重新赋值,还有就是地址指向的值是否可以重新赋值。
- 数组占有连续的内存单元,所以非常适合指针操作(copy等操作效率更高),所以数组名会被编译器自动隐式的转换成常量指针(有两个例外:对数组名取地址时;sizeof求数组内存大小时),所以数组名只能加一指向下一个元素,但是不能把下一个元素的地址赋值给数组名,所以数组名永远指向第一个元素的地址。
- 字符串在C语言中是常量,所以C语言是没有字符串类型的,而是使用字符数组或字符指针来描述字符串常量。
- 字符数组里保存的是字符串常量,所以值不能重新赋,但是数组元素是单个字符变量,所以可以重新赋值,即可以针对单个元素重新赋值。
- 字符指针保存的是字符串常量的地址,字符串常量本身被保存在常量区。所以,指针是可以重新赋值指向新的地址的。但是,指针指向的值是字符串常量,所以指向的值不可以重新赋成其他字符串。但是如果指针指向的是字符数组,则可以通过strcpy来重新赋值。
- sizeof(变量名):返回的是类型占用内存的大小,所以sizeof的参数可以是各种类型比如int,所以该值得大小在编译时就已经确定了。
- strlen(字符指针名/字符数组名):返回字符串中真正字符的个数。不包括结尾字符。所以该值得大小必须到运行时才能确定。
1. 一维数组代码demo:
/*
* author: tianqiang
* date : 2022/05/12
* description: 1)+1”就是偏移量问题:一个类型为T的指针的移动,是以sizeof(T)为移动单位。所以首先要明确指针类型。
* 2) 数组名表示数组首元素的地址, 数组名 + 1 表示第2个元素的地址;
* 3)对数组名取地址表示整个数组的地址,数组名取地址后再 + 1,表示尾指针,即紧跟数组最后一个元素的地址;
* 4)形象对比:中国政府在北京市,北京市的政府也在北京市,但是两者显然是不同的;
* 5)也可以认为,数组名表示的是一维数组,对数组名取地址可以认为是二维数组;
* 6) 数组名,&数组名[0], &数组名 三者值相同,但是第三个与前两个含义不同
* 7)数组名 + 1, &数组名[0] +1,值和意义都相同:第二个元素的地址
*/
#include <stdio.h>
int main () {
int scoreArray[10] = {99, 89, 78, 90};
printf("scoreArray size = %d \n", sizeof(scoreArray)); //数组占用的内存单元字节数:40
printf("scoreArray element = %d \n", sizeof(scoreArray)/sizeof(int)); //数组分配的元素个数:10
printf("scoreArray = %p, *scoreArray = %d \n", scoreArray, *scoreArray); //数组名是数组首元素的地址
printf("scoreArray + 1 = %p, *(scoreArray + 1) = %d \n", scoreArray + 1, *(scoreArray + 1)); //数组名+1为第二个元素的地址
printf("&scoreArray = %p, *(&scoreArray) = %p \n", &scoreArray, *(&scoreArray)); //数组名取地址,是整个数组的地址
printf("&scoreArray + 1 = %p, *(&scoreArray + 1) = %d \n", &scoreArray + 1, *(&scoreArray + 1)); //数组名取地址后再+1是尾指针
printf("&scoreArray[0] = %p \n", &scoreArray[0]);
printf("&scoreArray[0] + 1 = %p, *(&scoreArray[0] + 1) = %d \n", &scoreArray[0] + 1, *(&scoreArray[0] + 1)); //完全等同于数组名
return 0;
}
/* out put:
* scoreArray size = 40 //编译器分配内存的总字节数。
* scoreArray element = 10 //编译器分配总元素的个数,并不是实际元素的个数
* scoreArray = 0x7fff030bb710, *scoreArray = 99 //数组名表示首元素的地址 类型:int *
* scoreArray + 1 = 0x7fff030bb714, *(scoreArray + 1) = 89 //数组名 + 1:指向第二个元素
* &scoreArray = 0x7fff030bb710, *(&scoreArray) = 0x7fff030bb710 //&数组名:数组的地址,类型:int (*)[10]
* &scoreArray + 1 = 0x7fff030bb738, *(&scoreArray + 1) = -51099448 //&数组名+1:最后一个元素后面的地址
* &scoreArray[0] = 0x7fff030bb710
* &scoreArray[0] + 1 = 0x7fff030bb714, *(&scoreArray[0] + 1) = 89 //首元素地址+1:表示第二个元素的地址
*/
2. 二维数组代码demo:
/*
* author: tianqiang
* date : 2022/0512
* description: 1) 二维数组可以看成一个一维数组,其元素又是一个一维数组;
* 2)二维数组名表示第0行元素(一维数组)的地址,arr + i表示第i行元素(一维数组)的地址;
* 2)所以后面的内容就可以过渡到一维数组知识里
* 3)数组名就是指向数组第一个元素的指针,而其类型就是第一个元素的类型.
* 4)T类型的指针移动,以sizeof(T)为移动单位
* 5) 二维数组名aa, aa[0],*aa,&aa值相等,但是意义不同。
*/
#include <stdio.h>
int main () {
int array2[2][3] = {{1,2,3}, {44, 55, 66}};
printf("array2 = %p, *array2 = %p, **array2 = %d \n", array2, *array2, **array2);
printf("arrary2 + 1 = %p, *(array2 +1) = %p, **(array2 + 1) = %d \n", array2 + 1, *(array2 + 1), **(array2 + 1));
printf("array2[0] = %p, *array2[0] = %d, array2[0][0] = %d \n", array2[0], *array2[0], array2[0][0]);
return 0;
}
/*
* out put:
* array2 = 0x7ffe260e2b90, *array2 = 0x7ffe260e2b90, **array2 = 1 //二维数组名:第0行元素(一维数组)的地址
* arrary2 + 1 = 0x7ffe260e2b9c, *(array2 +1) = 0x7ffe260e2b9c, **(array2 + 1) = 44 //二维数组名 + 1:第1行元素(一维数组)的地址
* array2[0] = 0x7ffe260e2b90, *array2[0] = 1, array2[0][0] = 1
*
*/
3. 字符数组代码demo:
/*
* author: tianqiang
* date: 2022/05/13
* description: 1) 字符数组名是指针常量指向数组首元素,可以+1,但是不能重新赋值
* 2)字符指针名指向首字符,可以+1,也可以赋值,所以,字符指针名具体指向要根据上下文来定
* 3)sizeof返回编译器针对该类型分配的内存大小
* 4)strlen返回字符的实际个数,不包括'\0
* 5)%s输出字符串,需要提供地址,而不是值
* 6)%c输出字符,需要的是指,而不是地址
* 7)字符数组:字符串直接保存在字符数组的内存单元里;
* 8)字符指针:字符串被保存在常量区,字符指针保存了字符串的地址
*/
#include <stdio.h>
#include <string.h>
int main () {
char cArray[] = "tianQiang";
char cArrayError[35] = "qqqqq";
char *cPointer = cArray;
char *cPointerP = "tianQiang";
printf("cArray = %s, sizeof(cArray) = %d, strlen(cArray) = %d \n", cArray, sizeof(cArray), strlen(cArray));
printf("cArrayError = %s, strlen(cArrayError) = %d, sizeof(cArrayError) =%d \n", cArrayError, strlen(cArrayError), sizeof(cArrayError));
strcpy(cArrayError, cArray);
printf("cArray = %s, cArrayError = %s \n", cArray, cArrayError);
printf("strlen(cPointer) = %d, cPointer = %s, *cPointer = %c \n", strlen(cPointer), cPointer, *cPointer);
printf("strlen(cPointerP) = %d, cPointerP = %s, *cPointerP = %c \n", strlen(cPointerP), cPointerP, *cPointerP);
printf("cPointerP = ");
while(*cPointerP) {
printf(" %c", *(cPointerP++)); //字符指针可以重新赋值
}
printf("\n");
// cArray++; //编译报错:eror: lvalue required as increment operand
return 0;
}
/*
* out put
* cArray = tianQiang, sizeof(cArray) = 10, strlen(cArray) = 9 //sizeof返回编译器针对该类型分配的内存大小
* cArrayError = qqqqq, strlen(cArrayError) = 5, sizeof(cArrayError) =35 //strlen返回字符的实际个数,不包括'\0'
* cArray = tianQiang, cArrayError = tianQiang
* strlen(cPointer) = 9, cPointer = tianQiang, *cPointer = t //%s输出字符串,需要提供地址,而不是值
* strlen(cPointerP) = 9, cPointerP = tianQiang, *cPointerP = t //%c输出字符,需要的是指,而不是地址
* cPointerP = t i a n Q i a n g
*/
4. 数值型数组作为函数实参传递
* author: tianqiang
* date : 2022/0515
* description: 数值型数组名作为参数传递给函数
* 1) 数组名被自动转换成首元素的地址;
* 2) 遵守值传递,所以首元素的地址不会被改变;
* 3) 但是由于实行参共用同一块内存地址,所以值会同时改变。
*/
#include <stdio.h>
const int PASS_SCORE = 60;
void printMathScore(const float *p, int n); //参数为指针类型,需要提供第二个参数提供数组的大小。
void changeScore(float *p, int n);
//void changeScore(float score[], int n); //最终还是会转换成指针
int main () {
float mathScore[6] = {98, 37, 56, 61, 50, 49};
printMathScore(mathScore, sizeof(mathScore)/sizeof(float)); //调用时,数组大小一般是计算得到。
changeScore(mathScore, sizeof(mathScore)/sizeof(float));
printMathScore(mathScore, sizeof(mathScore)/sizeof(float));
return 0;
}
void changeScore(float *p, int n) {
for(int i = 0; i < n; i++) {
if ((*(p + i) < PASS_SCORE) && (PASS_SCORE - *(p + i) <= 10)) {
*(p + i) = PASS_SCORE;
}
}
}
void printMathScore(const float *p, int n) { //如果不允许数组值被改变,就需要使用const关键字。
printf("mathScore = ");
for (int i = 0; i < n; i++) {
printf("%f \t", *(p + i));
}
printf("\n");
}
/*
* out put: 可以看到实参和形参的值都被改变了,但是一定要理解,首元素的地址没有改变,所以还是值传递的。
* mathScore = 98.000000 37.000000 56.000000 61.000000 50.000000 49.000000
* mathScore = 98.000000 37.000000 60.000000 61.000000 60.000000 49.000000
*/
5. 字符数组/指针作为函数实参传递
/*
* author: tianqiang
* date :2022/05/15
* description: 字符数组/字符指针作为函数实参传递
* 1)字符指针由于有结尾标志,所以不需要传递size参数
* 2)温故知新:字符数组仅仅可以针对字符元素重新赋值;数组名对应的指针可以+1,
* 但是不能重新赋值,所以数组名永远指向首元素
* 3)温故知新:字符指针指向的是字符串常量,所以不能重新赋值。
* 但是字符指针本身可以重新赋值,指向其他地址的。
* 4)温故知新:字符指针指向字符数组后,就可以通过strcpy函数进行整体赋值
*/
#include <stdio.h>
#include <string.h>
void changeName(char *name);
void changeCharArrayName(char *name);
int main () {
char ch[] = "tianqiang"; //字符数组仅仅可以针对字符元素重新赋值
ch[0] = 'd';
//char *nameP = "tianqiang"; //字符指针是常量,不能重新赋值;
char *nameP = ch; //字符指针指向字符数组后,就可以通过strcpy函数进行整体赋值。
printf("nameP = %s \n", nameP);
changeName(nameP);
printf("after, nameP = %s \n", nameP);
ch[0] = 'd';
ch[1] = 'd';
ch[2] = 'd';
printf("ch = %s \n", ch);
changeCharArrayName(ch);
printf("after, ch = %s \n", ch);
}
void changeName(char *name) { //指向字符数组的指针可以用strcpy函数来重新赋值
strcpy(name, "jian");
}
void changeCharArrayName(char *name) { //char数组本身只能单个元素重新赋值
for (int i =0; i < strlen(name); i++) {
name[i] = 'm';
}
}
/*
* out put:
* nameP = dianqiang
* after, nameP = jian
* ch = dddn
* after, ch = mmmm
*/
6. 数组作为函数返回值
/*
2 * author: qianqiang
3 * date: 2022/0517
4 * description: 数组作为函数返回值
5 * 1)明确不能直接返回数组,而是返回该类型的指针
6 * 2)字符指针常量不能重新赋值,所以先指向字符数组,才可以重新赋值
7 */
8
9 #include <stdio.h>
10 #include <string.h>
11
12 int * returnScore(int *array, int size);
13 void printScore(int *p, int size);
14 char * returnName(char *p);
15
16 int main () {
17 int score[] = {88, 99, 56};
18 int *pScore = score;
19 char name[] = "tianqiangqi";
20 char *pName = name;
21 pScore = returnScore(score, sizeof(score)/sizeof(int));
22 printf("score = %p, pScore = %p \n", score, pScore);
23
24 printf("pScore = ");
25 printScore(pScore, sizeof(score)/sizeof(int));
26 printf("score = ");
27 printScore(score, sizeof(score)/sizeof(int));
28 printf("22222 score = %p, pScore = %p \n", score, pScore);
29
30 printf("pName = %s, pName addr = %p \n", pName, pName);
31 pName = returnName(name);
32 printf("pName = %s, pName addr = %p \n", pName, pName);
33
34 }
35
36 int * returnScore(int *p, int size) {
37 for(int i = 0; i < size; i++) {
38 *(p + i) = *(p+i) - 50;
39 }
40 return p;
41 }
42
43 char * returnName(char *p) {
44 strcpy(p, "jianmeiLiu");
45 return p;
46 }
47
48 void printScore(int *p, int size) {
49 for(int i = 0; i < size; i++) {
50 printf("%d \t", *(p + i));
51 }
52 printf("\n");
53 }
54 /*
55 * out put:
56 * score = 0x7ffeedc736a0, pScore = 0x7ffeedc736a0
57 * pScore = 38 49 6
58 * score = 38 49 6
59 * 22222 score = 0x7ffeedc736a0, pScore = 0x7ffeedc736a0
60 * pName = tianqiangqi, pName addr = 0x7ffeedc736ac
61 * pName = jianmeiLiu, pName addr = 0x7ffeedc736ac
62 *
63 */