9.多维数组与指针
(1) 数组名可以看做是指向第0个元素类型的指针常量,并且在数值上为第0个
个元素的地址。
数组名a,if 把a当做指针
a => &a[0]
(2) C语言中所有的数组都是一维数组。
假如有:
int a[3][4];
=> int[4] a[3]
a[0] _ _ _ _
a[1] _ _ _ _
a[2] _ _ _ _
表达式的含义 表达式的值
a (1)代表整个数组
(2)当做指针
&a[0] 第0行的地址
a[0] a[0]又是一个一维数组名
(1)代表整个一维数组a[0]
(2)当做指针
&a[0][0] 二维数组元素a[0][0]的地址
&a[0][0] typeof(&a[0][0]) 元素a[0][0]的地址
=> typeof(a[0][0]) *
=> int *
-----------------------------------------------------
a + 1 此处a当做指针来看
=> &a[0] + 1
=> &a[1] 第1行的地址
&a[1] a[1]又是一个一维数组名
此处只能当做整个数组来看
&a[1] 第1行的地址
&a + 1 此处a当做整个数组来看
typeof(&a)
=> typeof(a)*
=> int[4][3]*
&a + 1 : 往后面挪了1个int[4][3]
-------------------------------------------------------
a[1] + 2 a[1]又是一个一维数组名
在此处是当做指针
=> &a[1][0] + 2
=> &a[1][2] 元素a[1][2]的地址
*(a+1)+2 a在此处当做指针
=> *(&a[1]) + 2
=> a[1] + 2
=> &a[1][2] 元素a[1][2]的地址
&a[1][2] 元素a[1][2]的地址
------------------------------------------------------------------
*(a[2] + 2) a[2]又是一个一维数组名
此处a[2]当做指针
=> *(&a[2][0] + 2)
=> *(&a[2][2])
=> a[2][2] 元素a[2][2]
*(*(a + 2)+2) a在此处当做指针
=> *(*(&a[2])+2)
=> *(a[2] + 2)
=> *(&a[2][0] + 2)
=> *(&a[2][2])
=> a[2][2] 元素a[2][2]
a[2][2] 元素a[2][2]
10. 指针数组 与 数组指针
(1) 指针数组
指针数组是一个数组,只不过这个里面的元素都是指针
定义数组:
数组元素的类型 数组名[元素个数];
如:
int * p[4];
p是一个数组,里面含有4个元素,且每一个元素
都是int*类型的指针。
定义一个数组,数组的每一个元素都是一个指针,
这些指针分别指向a对应元素。
int a[5] = {1,2,3,4,5,};
int * p[5];
int i;
for(i = 0; i < 5; i++)
{
p[i] = &a[i];
}
假设需要通过数组p去访问a的每一个元素。
int sum = 0;
for(i = 0; i < 5; i++)
{
//sum += *p[i];
sum += *(*(p + i)) ;
}
(2) 数组指针
数组指针是一个指针,只不过这个指针是用来指向一个数组的罢辽。
也就是说这个指针保存的地址是一个数组的地址。
如:
int a[5] = {1,2,3,4,5};
能不能定义一个指针p,保存数组a的地址:
p = &a
p 指向 a
指向对象的类型 * p;
typeof(a) *p;
=> int[5] * p;
int (*p)[5];
//p = &a[0]; //ERROR typeof(&a[0]) => int*
typeof(p) => int[5]*
p = &a; // OK typeof(&a) => typeof(a)* => int[5]*
能不能通过指针p去访问整个数组a:
int i;
int sum = 0;
for(i = 0 ; i < 5; i++)
{
sum += *(*p + i) ;
}
例子:
int a[3][4];
=> int[4] a[3]
a[0] _ _ _ _
a[1] _ _ _ _
a[2] _ _ _ _
定义一个指针p,用来保存a[0]的地址: &a[0]
typeof(a[0]) * p;
=> int[4] *p;
=> int *p[4];
int (*p)[4];
//p = a; //OK
p = &a[0];
11. 字符串与指针
字符串是一串字符。C语言中,没有字符串这个类型。
C语言中的字符串是通过 char*(字符指针)来实现的。C语言的字符串
使用""(双引号)引起来的一串字符来表示,并且字符串后面默认会加一个
'\0', \0(ASCII码的值为0的那个字符)字符串结束的标志。
如:
"abc"
"123\0xxx"
"\0"
""
我们只需要保存字符串的首地址(首个字符的地址)就可以了,从首地址
开始找到第一个\0,前面的这些字符就是字符串里面的字符啦!!
C语言中的字符串(如: "sssssabc")是保存在一个叫 .rodata(只读数据)的内存区域。
字符串代表是这个内存空间的首地址。
如:
"12345"
在程序运行时,系统会为这个字符串在 .rodata 中分配6个字节的空间去保存
上述字符串。表达式 "12345" 的值是,就是首字符的地址。
在C语言中,字符串的值 就是 它对应首字符的地址。
"12345" => &'1'
typeof("12345")
=> typeof(&'1')
=> typeof('1')*
=> const char * (只能读取,不可改变)
例子:
char * p = "12345";
p保存的是字符串首字符的地址,'1'的地址
p + 1 '2'的地址
*(p + 1) => *(&'2') => '2'
printf("%c\n", *p); //1
printf("%c\n", *(p + 1)); //2
*(p + 1) = 'B'; //ERROR p指向的字符串,是保存在.rodata中
//.rodata中的内容,不能被修改!!!
字符数组:
char s[] = {"12345"};
//定义了一个数组,数组名为s,s是一个里面含有6个char类型的元素的数组
=>
char s[] = {'1', '2','3','4','5','\0'};
sizeof(s) == 6
=>
char s[6] = {'1', '2','3','4','5','\0'};
sizeof(s) == 6
char s[] = {'1', '2','3','4','5'};
sizeof(s) == 5
char s[5] = {'1', '2','3','4','5'};
sizeof(s) == 5
例子:
char p[] = {"12345"}; //p是一个数组,数组开辟的空间在 "栈空间"
=> char p[] = {'1', '2','3','4','5','\0'};
//char ch = p[1]; //ch == '2'
char ch = *(p + 1);
*(p + 1) = 'B'; //OK <=> p[1] = 'B'
printf("%s\n", p); // 1B345
//%s -> char * 地址
//把后面的那个地址当做是一个字符串的首地址,一个一个字符
//输出,直到遇到了'\0' 或者 出错了 才会结束。
练习: test6.c
写一个函数,求一个字符串的长度(包含了多个有效字符,除去'\0')
/*
my_strlen : 求一个字符串的长度
@p : 指向要求的那一个字符串
const char * p
返回值:
返回字符串的有效字符个数
int
*/
int my_strlen(const char * p)
{
int count = 0;
while(*p) //*p != '\0'
{
count ++;
p++;
}
return count;
}
12. 几个常用的字符串的处理函数(标准库里面已经给大家实现)
#include <string.h>
man -f strlen
x-手册页
(1) strlen: 用来求一个字符串的长度(不包含'\0')
NAME
strlen - calculate the length of a string
SYNOPSIS
#include <string.h>
size_t strlen(const char *s);
@s: 要计算长度的字符串的首地址
返回值:
返回s指向的那一个字符串的长度(不包含'\0')
DESCRIPTION
The strlen() function calculates the length of the string pointed to by
s, excluding the terminating null byte ('\0').
RETURN VALUE
The strlen() function returns the number of characters in the string
pointed to by s.
例子:
strlen("abcde") == ? 5
char s[4] = {'1', '0'};
int l;
l = strlen(s) ;
l == ? 2
sizeof(s) == ? 4
strlen("abcd\nabc") == ? 8
strlen("123\123abc\0abc") == ? 7
strlen("123\0123abc\0abc") == ? 8
(2) strcpy/strncpy
字符串拷贝函数
NAME
strcpy, strncpy - copy a string
SYNOPSIS
#include <string.h>
strcpy :用来把src指向的字符串,拷贝到dest指向的空间中去。
直到遇到'\0'拷贝结束!
char *strcpy(char *dest, const char *src);
@dest: destination 目标,目的地
@src : source 源,从哪里
返回值:
返回拷贝后目的地字符串的首地址。
例子:
char s[6];
strcpy(s, "12345");
printf("s == %s\n", s); //s == 12345
但是,strcpy有一个巨大的bug!!!
它没有考虑到越界的问题,可能会导致内存的越界或
非法访问!!!
例子: test7.c
char s1[8];
char s2[8] = "ABCDE";
printf("s1 = %p\ns2 = %p\n", s1, s2);
//编译器一般会把s1,s2放到连续的地址空间上去
strcpy(s1, "123456789");
printf("s1 = %s\n", s1); //123456789
printf("s2 = %s\n", s2); //9
-----------------------
char *strncpy(char *dest, const char *src, size_t n);
strncpy 正是为了解决strcpy的这个bug的
strncpy :把src指向的字符串前面的顶多n个字符拷贝到dest指向的空间去。
strncpy的功能与strcpy类似,只不过它顶多是拷贝n个字符
strncpy到底是拷贝了多少个字符呢?
(1) 遇到'\0'拷贝结束,此时'\0'也会被拷贝
(2) 已经拷贝了n个字符啦(后面的字符就不会被拷贝啦)
例子:
char s1[8];
char s2[8] = "ABCDE";
printf("s1 = %p\ns2 = %p\n", s1, s2);
//编译器一般会把s1,s2放到连续的地址空间上去
strncpy(s1, "123456789", 8);
printf("s1 = %s\n", s1); //12345678ABCDE
printf("s2 = %s\n", s2); //ABCDE
(3) strcmp/strncmp
字符串比较函数
字符串是如何比较的呢?
一个一个字符比较它们对应的ASCII的值。
if c1 > c2
返回 1
if c1 < c2
返回 -1
if c1 == c2
则继续比较下一个字符,如果全部相等,则返回0
strcmp("123456", "ABC") < 0
strcmp("1234", "123\0abc") == 0
strcmp("1234", "123\0abc" ) > 0
strncmp 和 strcmp功能是类似的,都是用来比较两个字符串的。
但是strncmp最多比较两个字符串的前面的n个字符!!
strcmp("1234", "123\0abc" ) > 0
strncmp("1234", "123\0abc" , 3) == 0
strncmp("123456", "ABC", 2) < 0
strncmp("123456", "1232", 4) > 0
NAME
strcmp, strncmp - compare two strings
SYNOPSIS
#include <string.h>
int strcmp(const char *s1, const char *s2);
int strncmp(const char *s1, const char *s2, size_t n);
DESCRIPTION
The strcmp() function compares the two strings s1 and s2. It returns
an integer less than, equal to, or greater than zero if s1 is found,
respectively, to be less than, to match, or be greater than s2.
The strncmp() function is similar, except it compares only the first
(at most) n bytes of s1 and s2.
RETURN VALUE
The strcmp() and strncmp() functions return an integer less than, equal
to, or greater than zero if s1 (or the first n bytes thereof) is found,
respectively, to be less than, to match, or be greater than s2.
(3) strcat/strncat
字符串连接函数
NAME
strcat, strncat - concatenate(连接) two strings
SYNOPSIS
#include <string.h>
strcat: 用来把src指向的字符串拷贝到dest指向的字符串的末尾(尾部连接)
char *strcat(char *dest, const char *src);
@dest: 指向目标字符串(一段空间)
dest指向的空间,必须要足够大?
why? 越界问题
@src: 指向原始字符串(待拷贝字符串)
返回值:
返回dest指向的那一个首地址。
例子:
char s1[12] = {"ABCD"};
strcat(s1, "123456789");
printf("s1 == %s\n", s1); //ABCD12345
strcat也有一个巨大的bug!!! 你懂的
so,strncat就是用来解决strcat的bug的。
strncat:把src指向的字符串拷贝到dest的末尾,但是它顶多能拷贝
n个字符。那么strncat到底拷贝了多少个字符?
(1) 可能没有到第n个字符,但是就已经遇到了'\0'
(2) 即使没有遇到'\0',但是已经拷贝了n个字符啦!!
char *strncat(char *dest, const char *src, size_t n);
例子:
1. 分析如下程序的输出结果
char s1[12];
strcat(s1, "12345");
printf("%s\n", s1); //undefine 随机值
练习:test8.c
写一个函数,把一个十进制的数字字符串,转成一个整数
"123" => 123
"-456" => -456
/*
my_atoi: 将一个数字字符串转变为一个整数值
@s : s指向数字字符串
返回值:
转变之后的整数值 => int
*/
int my_atoi(char * s)
{
int d = 0;//保存当前位的数值
int num = 0;//整数值
int minus = 0;// 1 - 负数
if(*s == '-')
{
minus = 1;
s++;
}
else if(*s == '+')
{
minus = 0;
s++;
}
//*s肯定是一个数字字符
while(*s)
{
d = *s - '0';
num = num*10 + d;
s++;
}
if(minus)
{
num = num * (-1);
}
return num;
}
(4) atoi
其实在标准库也给大家提供了,
需要用时,直接调用即可!!
NAME
atoi, atol, atoll - convert a string to an integer
SYNOPSIS
#include <stdlib.h>
int atoi(const char *nptr);
long atol(const char *nptr);
long long atoll(const char *nptr);
13. 二级指针以及多级指针
指向对象的类型 * 指针变量名;
int a = 1024;
可以定义一个指针变量p,来保存a的地址:
typeof(a) * p;
=> int * p;
p = &a; //p 指向 a
保存普通变量的地址的指针p为 一级指针
可不可以定义另外的一个指针变量q,来保存p的地址:
typeof(p) * q;
=> int* * q;
q = &p; //q 指向 p
保存一级指针变量的地址的指针q为 二级指针
可以定义一个指针变量t,来保存q的地址:
typeof(q) * t;
=> int** * t;
t = &q; //t 指向 q
保存二级指针变量的地址的指针t为 三级指针