---------------------- Java培训、Android培训、iOS培训、.Net培训,期待与您交流! ---------------------
一、数组
1. 数组的定义
元素类型 数组名[元素个数];
元素类型决定数组存放的元素的数据类型是固定的,元素个数决定了数组需要多少的存储空间。
数组元素是有序的,每个元素都有一个唯一的下标,下标从0开始。
//数组的定义格式: 类型 数组名[元素个数]
int ages[5] = {10, 5, 6, 32, 9};
int ages[5] = {12, 6};
int ages[5] = {[3]=5, [4]=45};
int ages[] = {102, 45, 1};
/*错误写法
int ages[];
int ages[5];
ages = {10, 54, 9, 6, 3};
*/
/*正确写法
int ages['A'-50] = {10,5,5,6,3};
int size = sizeof(ages);
printf("%d\n", size);
int count = 5;
int ages[cont];
ages[0] = 5;
ages[1] = 38;
ages[2] = 7;
*/
//错误写法
//如果想在定义数组的同时进行初始化,数组元素的个数必须是常量,或不写。
int ages[count] = {10, 11, 120}; // 这里count是变量。。
//计算元素的个数
int count = sizeof(ages)/sizeof(int);
for (int i=0; i<count; i++) {
printf("ages[%d]=%d\n", i, ages[i]);
}
2. 数组的内存分析
数组所占的存储空间大小是由元素类型和元素个数决定的。
数组的内存分配是从高地址到低地址进行的,但一个数组内部元素又是从低到高进行的。(低地址分配低下标元素,高地址分配高下标元素)
char cs[5] = {'a', 'A', 'D', 'e', 'f'};
//数组名代表数组的地址,不用&符号
printf("%p\n", cs);
for (int i=0; i<5; i++) {
printf("cs[%d]的地址是:%p\n", i, &cs[i]);
}
3. 二维数组
类型 数组名[行数][列数];
二维数组是一个特殊的一维数组,它的元素是一维数组。
/*
函数day_of_year将某月某日的日期表示形式转换为某一年中的第几天的表示形式,函数month_day则执行相反的转换。
*/
void test()
{
static char daytab[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
int day_of_year(int year, int month, int day)
{
int i, leap;
leap = year%4 == 0 && year%100 != 0 || year%400 == 0; // 闰年 leap == 1
for (i = 1; i < month; i++)
day += daytab[leap][i];
return day;
}
void month_day(int year, int yearday, int *pmonth, int *pday)
{
int i, leap;
leap = year%4 == 0 && year%100 != 0 || year%400 == 0; // 闰年 leap == 1
for (i = 1; yearday > daytab[leap][i]; i++)
yearday -= daytab[leap][i];
*pmonth = i;
*pday = yearday;
}
}
4. 数组当做函数参数
数组作为函数参数时,元素个数可以省略不写。
把数组名作为实参传递给函数,传递给形参的是数组所在的首地址,是地址传递。
在函数内部修改数组的元素内容,会影响函数外部的数组内容。
5. 字符数组与字符串
(1)字符数组:
char name[] = {'j' + 'o' + 's' + 'e'};
存放的元素是字符类型char的数组。
(2)字符串
char name[] = "jose";
同样是存放元素是字符类型char的数组,但是最后隐含一个字符’\0‘为字符串的结束标志。
也就是说这个数组的元素个数比字符数组大1。
'\0'的作用:
1》%s是以\0为结束标志的字符串输出格式。而且'\0'是不会输出的。
2》\0 也是一个字符,占一个字节。
(3)strlen函数
计算字符串的长度。
1》计算的是字符数,并不是字数
2》计算的字符不包括'\0'
3》一个汉字算3个字符长度
4》strlen函数计算字符串,直到遇到'\0'为止。
6. 字符串数组
char names[2][10] = {"hehe", "haha"};
存放的元素是字符串的数组。
二、指针
1. 指针的定义
类型 *变量名;
类型是指针变量所指向的数据的数据类型,变量名为指针变量的名称。
定义指针变量的*仅仅是一个象征,没有其他特殊含义。
指针变量只能存放地址值,指针也就一个作用:通过指针变量存储的地址值,访问对应的存储空间。
一元运算符*是间接寻址或间接引用运算符,当它作用于指针时,将访问指针所指向的对象。
任何指针都占用8个字节的存储空间。
sizeof()函数的返回值类型是unsigned long;
int *p;
int a = 90;
p = &a;//指针变量p指向了变量a
*p = 10;
【指针的使用注意】:清空指针:p = 0; 或 p = NULL;
注:指针被清空后,就不能再拿来使用了,不能再间接访问其他存储空间了。
/*不正确,int *p只能指向int类型的数据
int *p;
double b = 10.0;
p = &b;
*/
/*不正确,指针变量只能存储地址
int *p;
p = 200;
*/
/*不正确,指针变量未经初始化,不要拿来间接访问其他存储空间
int *p;
printf("%d\n", *p);
*/
int a = 10;
int *p = &a;//定义变量时的*仅仅是一个象征,没有其他特殊含义
//*p = &a;//不正确
*p = 20;//*p的*的作用:访问指针变量p指向的存储空间
2. 指针作为函数参数
当指针作为形参时,函数调用者传递的实参必须是一个地址。(有时候可以用地址符&来当做实参传递)
这时候函数内部改变指针所指向的值,就相当于改变函数外部的值。
#include <stdio.h>
void swap(int *v1, int *v2);
int main()
{
int a = 10;
int b = 94;
swap(&a, &b);
printf("a=%d, b=%d\n", a, b);
return 0;
}
//成功交换
void swap(int *v1, int *v2)
{
int temp;
temp = *v1;
*v1 = *v2;
*v2 = temp;
}
/*
//只交换了v1和v2这两个指针变量的内容,即地址
void swap(int *v1, int *v2)
{
int *temp;
temp = v1;
v1 = v2;
v2 = temp;
}
*/
/*
//只是交换了函数内局部变量v1,v2的值
void swap(int v1, int v2)
{
int temp;
temp = v1;
v1 = v2;
v2 = temp;
}
*/
由于C语言是以值传递方式将参数值传递给被调用函数,所以被调用函数不能直接修改主函数中的变量的值,通过指针可以改变这一点。
好处:指针参数是的被调用函数能够访问和修改主调函数中对象的值,因此我们可以使函数有多个返回值。
3. 返回指针的函数
类型名 *函数名(参数列表)
#include <stdio.h>
char *test();
int main()
{
char *name = test();//用来返回字符串
printf("name=%s\n", name);
return 0;
}
char *test()//返回指针类型
{
return "rose";
}
4. 指向函数的指针
函数作为一个段程序,在内存也要占据存储空间,它也有一个起始地址,这就是函数的入口地址。可以用指针变量来存放函数的入口地址,这样我们就有了一个指向函数的指针。其实在C语言中,函数名就代表着函数的地址。
定义的格式:
返回值类型 (*指针变量名)(形参1, 形参2, ...)
//(*p)固定写法,代表指针变量p将来肯定是指向函数的
//左边的void:指针变量p指向的函数没有返回值
//右边的():指针变量p指向的函数没有形参
void (*p)();
p = sum;//p = 函数的地址
//(*p)();//利用指针变量间接调用test函数
p();
指向函数的指针的2个作用:
1》间接调用函数
2》将函数作为参数进行参数传递
5. 指针和数组
数组的访问方式:
1》数组名[下标]
2》指针变量名[下标]
3》*(p+i)
#include <stdio.h>
void change(int array[]);
int main()
{
int ages[5] = {10, 652, 6, 5, 88};
//change(ages);
change(&ages[1]);
return 0;
}
//利用一个指针变量来接受一个数组,指针变量array指向了首元素
void change(int *array)
{
printf("%d\n", *(array+2));//*array是取出首元素的值
}
p[i]和*(p+i)是等价的:一个通过数组和下标实现的表达式等价于通过指针和偏移值的表达式。
但是,我们必须记住,数组名和指针之间有一个不同之处:指针是一个变量,对指针变量的算术运算是合法的;数组名不是变量,对数组名的算术运算是非法的。
》》合法的指针运算:
1》相同类型指针之间的赋值运算;
2》指针同整数之间的加法或减法运算;
3》指向相同数组中元素的两个指针间的减法或比较运算;
4》将指针赋值为0或指针与0直接的比较运算。
6. 指针和字符串
char *name = "world";
定义字符串的2种方式:
1》利用数组(字符串变量)
char name[] = "hello";
特点:字符串里面的字符是可以修改的。
使用场合:字符串的内容需要经常修改。
2》利用指针(字符串常量)
特点:字符串其实就是一个常量字符串,里面的字符是不能够修改的。
使用场合:字符串的内容不需要修改,而且这个字符串是经常用到的,并且长期在内存中。
如果试图修改字符串常量里面的数据,系统就会报错:Bus error: 10
7. 指向指针的指针
类型名 **变量名;
int *p;
int **pp = &p;
首先p要先是一个指针,之后pp才需要两个*来定义指向p的指针。
#include <stdio.h>
int main()
{
int a = 10;
int *p = &a;//int *
int **pp = &p;//int **可以当做一个类型:指向指针的指针。**一个象征而已,调用还是一个*
//int ***ppp = &pp;
//修改a的两种间接方法:
*p = 20;
*(*pp) = 30;// **pp == *p == a
//***ppp = 40;
printf("%d\n", a);
return 0;
}<span style="font-family:SimHei;font-size:18px;"><strong>
</strong></span>
8. 指针与多维数组
char *name[] = {"hello", "today is sunny", "hi"};
char name[][15] = {"hello", "today is sunny", "hi"};
指针数组最大的好处就是存放具有不同长度的字符串,而不浪费存储空间;
二维数组每个元素的存储空间都是固定不变的,如果存放不同长度的字符串,则要定义每个字符串的大小且浪费存储空间。