------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
1.数组
数组是一种构造类型.
数组用来存储一组相同类型的数据.可以是任何类型的.但必须是同一种类型的数据,存放的数据被称为元素。
格式: 类型数组名[元素个数];
int a[5];
错误写法:int a[]; //定义时不可以不指定元素个数
初始化
初始化是指在数组定义的同时给数组元素赋值.
注意:
只有在定义时,并且元素个数是常量[表达式]时,才可以用{}初始化,未被初始化的元素会被自动赋值为0.
(常量可以是 数字, #define的宏, const变量,字符常量等).
初始化的方式
a.int a[5] = {1, 2, 3, 4, 5};
b.int a[] = {1, 2, 3, 4, 5};初始化时可以省略元素个数,相当于 int a[5] = {...};
c.int a[5] = {1, 2};初始化部分元素:只给前两个元素赋值,其它元素默认为0.
d.int a[5] = {[3] = 4, [4] = 5};初始化指定元素:只给a[3]和a[4]赋值,其它元素默认为0
e.int a[5] = {};所有元素赋值为0
f.int a['A'-60] = {1, 2, 3};相当于 int a[5] = {...};
错误写法
a. int a[5];
a = {1, 2, 3, 4, 5}; // 错误 {}初始化只能在定义时使用
定义后数组名就是个常量,不能被赋值。所以必须要逐个元素来赋值
b.. int n = 5;int a[n] = {1, 2, 3, 4, 5}; //错误
元素个数必须为常量或常量表达式时才能在定义时初始化.如果是变量或变量表达式,不能初始化
但是可以这样:int a[n];a[0] = 1; a[1] = 2;
这样是可以的,注意这样就不是初始化了,没有赋值的元素的值不是0,而是任意数.
数组元素的赋值
a[0] = 1;a[1] = 2;
注意:
如果数组经过{}初始化后,没有被主动赋值的元素值为0.如果是在定义后逐个赋值,没有被主动赋值的元素值可能为任意值.
C语言是弱语法,编译器不会对数组下标越界检查,自己要注意。
计算数组元素个数
int n = sizeof(a) / sizeof (int);
注意:
如果把数组名作为函数参数,在函数内部是无法获得数组的元素个数的。因为函数参数接收的是数组的地址,所以使用sizeof(a)打印出的是指针的大小8个字节,而不是整个数组所占的字节数。所以必须要把数组中元素的个数作为参数传递给函数。
数组元素的遍历
遍历:按顺序查看数组的每一个元素
for (i = 0; i < n; i++)
printf("a[%d] = %d\n", i, a[i]);
数组在内存中的存储
数组名
a.数组名就是数组的地址
b.数组名是个常量,不能被赋值(所以数组在定义后就不可以使用{ }赋值,只能逐个元素的赋值)
数组占用的内存空间
a.数组在内存中存储为一段连续的内存空间,比如一个int a[2]的数组,所占空间为4 * 2 = 8个字节。
b.sizeof(a) 来看数组占用字节数
数组的地址和它的每个元素的地址
a.数组中的元素在内存中按照由小到大的地址排列
b.数组的地址是它的起始地址(最小地址),等于数组名的值
c.可以使用 prinf("%p", &a[i]);来查看元素/数组在内存中的地址
数组与函数
使用数组作为函数参数
由于数组名代表的是数组的地址,所以形参复制了实参的地址,和实参指向同一个数组,修改形参数组中的内容能够修改实参数组中的内容。
void change (int a[])
{ // 使用数组作为函数参数
a[0] = -1;
}
change(a); // 传递数组地址
注:形参可以不写元素个数,但是[]必须要写,表示参数是数组类型。实参只写数组名,不用写[]
传递的是数组名,即数组的地址,所以函数操作的和实参是同一个数组,能够修改原数组元素的值.
使用数组元素作为函数参数
由于数组元素是基本类型变量,只是简单的值传递,修改形参不改变原数组元素的值
void change(int n) { // 使用基本数据类型作为函数参数
n = -1;
}
change(a[0]); // 不改变参数的值
参数是基本数据类型变量,只是简单的值传递.
函数与数组元素的个数
比如 int change(array[]){ };
我们把数组名传递到函数中,在函数中再打印数组名的size,会发现打印出的结果总是8个字节.这是因为传递到函数中的其实是个数组指针,所以sizeof(array)返回的是数组指针在内存中占据的空间大小,而不是实参数组所占据的空间.
所以在C语言中,如果函数需要知道数组大小,传递数组地址的同时,还需要传递给函数这个数组中元素的个数.
2.字符串
字符串其实就是一个以\0结尾的字符数组.
字符串通常以""表示,使用""会自动在后面加'\0'字符
组成
字符串由很多个字符组成,以字符'\0'结束.
初始化
字符串就是一个以'\0'结束的字符数组
a.char name[10] = {'J', 'a', 'c', 'k', '\0'};
b.char name[10] = {'J', 'a', 'c', 'k', 0};因为'\0'的ASCII码值为0
注意
a.char name[10] = {'a', 'b'};
也是一个字符串,因为字符数组默认后面的值都是0 (即'\0')
b.但是
char name[2] = {'a', 'b'};
char name[] = {'a', 'b'};就不是字符串了,因为它们只有两个元素,不以\0结束.
字符串都是字符数组,但不是所有的字符数组都是字符串.
c.通常还是使用双引号来定义
char name[10] = "Jack"; //使用双引号会在末尾自动添加'\0'
char name[] = "Jack"; //可以不指定数组大小
\0的作用
如果定义一个字符串和一个普通的字符数组,如果我们用字符串方法操作字符输出(比如用%s输出),会发现把前面的字符串都输出出来了.
char name[] = "abc";
char name2[] = {'d', 'e'};
printf("%s\n", name2);
printf函数打印字符串的原理
printf函数中使用name2作为参数,这个数组名其实就是传递了数组name2的地址, printf("%s",a)这个函数就是从提供的地址开始输出字符,一直到\0结束输出.所以,如果一个字符串不以'\0'结束,那么就会一直打印,直到遇到一个'\0'为止,如果打印到未被赋值的内存区域,就会打印出乱码。
字符串函数 strlen
这个函数计算一个字符串的字符个数,不包括'\0',是在<string.h>中声明的
#include <string.h>
strlen("Jack"); //返回 4
strlen("哈Jack"); //返回 7,一个汉字占三个字符
注意
a.和sizeof区分, sizeof计算占的字节数(包括字符串中的'\0'), strlen计算字符个数
b.strlen()函数也是从给的字符串地址开始计算字符数,直到遇到'\0'为止
字符串的遍历
编写一个函数来判断一个字符串中是否包含某个特定字符,如果包含就返回1,否则返回0.
使用for循环
#include <string.h>
int contains_char(char str[], char c) {
for (int i= 0; i < strlen(str); i++){
if (str[i] == c)
return 1;
}
return 0;
}
使用while 循环
i
int contains_char(char str[], char c)
{
int i = 0;
while(str[i] != '\0')
{ //可以写成while(str[i]) 因为'\0'的值是0, 不是0就是真值.
if (str[i] == c)
return 1;
i++;
}
return 0;
}
字符串数组
装字符串的数组,比如储存多个人的名字:
char names[3][10] = {
"Jack",
"Maryl",
"Den"
};
printf("The second person is %s\n", names[1]); //获取其中一个字符串
printf("The last name ends with letter %c\n", names[2][3]); //获取其中某个字符串中的一个字符
3.指针
指针变量
作用存储内存地址的变量.根据这个地址就可以操作(获取或修改)这块存储空间中的内容.
格式 类型标识符 *变量名int *p;
指针可以用于指向任何类型的变量(比如数组,结构体,其它指针等),所以需要定义同种类型的指针.
初始化int *p;
p = 10;
或者int *p = 10;
注意指针变量只能用来存地址
指针没初始化不要随便用来访问存储空间
指针的加法
在数组中常常会使用*(p+i)这种形式,指针可以和数字相加,指针的类型决定了当指针+1时跳过多少个字节。比如int 4 ,double 8, char 1.....
指针变量占的空间大小
不管指针指向什么类型的数据,它存储的都是内存地址。所以所有的指针变量所占的空间都是一样的,与指针变量的类型无关,只跟编译器有关。在Xcode中,指针变量占8个字节
指针与数组
利用指针来接收一个数组,指针变量指向的了数组的首元素(数组的地址)
数组元素的访问方式
a.数组名[下标];age[i];
b.指针变量[下标];p[i];
c.*(age+i)
d.*(p+i) == *(age+i)//注意 age为指针常量 p为指针变量(p++)
数组指针和函数参数
如果一个函数要操作一个数组,可以使用数组作为函数参数,也可以使用数组指针作为函数参数,二者是等价的,都可以修改数组中的元素。
调用的时候,实参都是数组名,因为数组名就是数组首元素的地址。
使用数组作为函数参数
void change ( int array[])
{ //形参是数组 实际为 int *array 接收一个地址 所以两者等价
// int * 说明其只能指向一个数组元素
array[0] = 100; //修改数组的元素
}
int a[3]={1,2,3};
change(a); //实参是数组名(指针,即是个地址)
当函数的参数是个数组的时候,调用时实参可以是数组名,也可以是指向数组(数组元素)的指针
字符串指针
字符串的定义
利用数组 char name[] = "itcast";
特点:这样定义的字符串其实是一个变量字符串,里面的字符是可以修改的。
利用指针 char *name = "itcast";
特点:这样定义的字符串其实是一个常量字符串,里面的字符是不能修改的。
字符指针数组
定义一个字符指针数组:
char *name[2] = {"jack","rose"};
其实用字符串数组也可实现上面的存储:
char name[2][10] = {"jack","rose"};
注意
char s[5]; char *p;
s = "abc"; p = "abc";
错误!s是数组首地址,是个常量 正确!p是字符指针,可以指向一个字符串。
char s[5]="abc" 正确!s是一个字符数组,可以在定义的时候初始化,这个"abc"是一个字符串变量。
char *p = "abc"; 正确!p是一个字符指针,可以指向一个字符数组,这个"abc"是一个字符串常量。
char *p = "abc";
*p = "abcd";错误!*p指向的"abc"是一个字符串常量,不可以被重新赋值。
char *p = "abc";
p = "abcd"; 正确!可以把p指向一个新的字符串(也是常量)。
char s[5] = "abc";
s[0] = 'A'; 正确!字符数组指向的是可变字符串,可以修改字符串中的内容。
char *p = "abc";
p[0] = 'A'; 错误!字符指针指向的是不可变字符串,不能修改字符串中的字符。
char s[] = "abc";
char *p = s;
p[0] = 'A'; 正确!p虽然是个指针,但是它现在指向的是s[]定义的可变字符串,所有可以修改字符串的内容。
小结:
用数组形式s[]定义的是字符串变量,可以像数组一样访问,可以修改字符串的内容,比如s[0] = 'A'。
用指针形式*p定义的是字符串常量,不可以修改字符串的内容(比如p[0] = 'A';或 *p = "ABC";),但是可以指向一个新的字符串(比如 p = "ABC";),也可以指向一个可变字符串,比如p = s;就可以修改这个字符串中的内容了 p[0] = 'A'; 如果再次指向一个新定义的字符串(比如 p = "abc"),那还是字符串常量,不可修改。
使用场合
如果字符串的内容需要被修改,使用char[]方式定义字符串变量
如果字符串的内容经常使用但不需要修改,使用char*方式定义字符串常量。
函数指针
指向函数的指针
定义形式:函数的返回值类型 (*指针变量名)(形参1,形参2, ...);
比如int sum (int a, int b)
{int c = a + b;
return c;}
可以定义一个同类型的指针:
int (*p)(int, int) ; //这样定义函数指针,用(*p)来代替函数名的位置
p = sum; // 函数名就是函数的地址,p获得了sum函数的地址,指向这个函数
注意
a.使用指针变量p来间接调用sum函数:
(*p)(5, 6); //小括号不可省略
p(5, 6); //相当于sum(5,6)
b.由于这类指针变量存储的是一个函数的入口地址,所以对它们作加减运算(比如p++)是无意义的
c.指向函数的指针变量主要有两个用途:调用函数,将函数作为参数在函数间传递
返回指针的函数
类型名 *函数名(参数列表)
char *test ()
{
return "rose";
}
int main()
{
char *name = test();
return 0;
}