C语言复习第六天
指针的本质分析
*号的意义
- 在指针声明时,*号表示所声明的变量为指针
- 在指针使用时,*号表示取指针所指向的内存空间的值
tips
int i=0;
int j=0;
int *p=&i; 变量p保存着变量i的内存地址,即: p<–>&i *p<---->i
j=*p;
使用示例:
#include <stdio.h>
int main()
{
int i = 0;
int* pI;
char* pC;
float* pF;
pI = &i;
*pI = 10;
printf("%p, %p, %d\n", pI, &i, i);
printf("%d, %d, %p\n", sizeof(int*), sizeof(pI), &pI);
printf("%d, %d, %p\n", sizeof(char*), sizeof(pC), &pC);
printf("%d, %d, %p\n", sizeof(float*), sizeof(pF), &pF);
return 0;
}
tips
64位系统里指针变量都是8个字节,因为地址是用8个字节去表示。
传值调用与传址调用
- 指针是变量,因此可以声明指针参数
- 当一个函数体内部需要改变实参的值,则需要使用指针参数
- 函数调用时实参值将复制到形参
- 指针适用于复杂数据类型作为参数的函数中
使用示例(交换两个变量的值):
#include <stdio.h>
int swap(int* a, int* b)
{
int c = *a;
*a = *b;
*b = c;
}
int main()
{
int aa = 1;
int bb = 2;
printf("aa = %d, bb = %d\n", aa, bb);
swap(&aa, &bb);
printf("aa = %d, bb = %d\n", aa, bb);
return 0;
}
常量与指针
const int *p; //p可以改变,p指向的内容不能修改
int const* p; //p可以改变,p指向的内容不能修改
int *const p; //p不可以改变,p指向的内容可以修改
const int* const p; //都不可以改变
//口诀: (*号)左数右指(不能修改,为常量)
示例:
```c
#include <stdio.h>
int main()
{
int i = 0;
const int* p1 = &i;
int const* p2 = &i;
int* const p3 = &i;
const int* const p4 = &i;
*p1 = 1; // compile error
p1 = NULL; // ok
*p2 = 2; // compile error
p2 = NULL; // ok
*p3 = 3; // ok
p3 = NULL; // compile error
*p4 = 4; // compile error
p4 = NULL; // compile error
return 0;
}
数组的本质分析
- 数组是相同类型变量的有序集合
数组的大小
- 数组在一片连续的内存空间中存储元素
- 数组元素的个数可以显示或隐式指定。
int a[5]={1,2};
int b[l]={1,2};
编程示例:
#include <stdio.h>
int main()
{
int a[5] = {1, 2};
int b[] = {1, 2};
printf("a[2] = %d\n", a[2]);
printf("a[3] = %d\n", a[3]);
printf("a[4] = %d\n", a[4]);
printf("sizeof(a) = %d\n", sizeof(a));
printf("sizeof(b) = %d\n", sizeof(b));
printf("count for a: %d\n", sizeof(a)/sizeof(int));
printf("count for b: %d\n", sizeof(b)/sizeof(int));
return 0;
}
数组地址与数组名
- 数组名代表数组首元素的地址
- 数组的地址需要用取地址符&才能得到
- 数组首元素的地址值与数组的地址值相同
- 数组的首元素的地址与数组的地址是两个不同的概念
#include <stdio.h>
int main()
{
int a[5] = { 0 };
printf("a = %p\n", a);
printf("&a = %p\n", &a);
printf("&a[0] = %p\n", &a[0]);
return 0;
}
数组名的盲点
- 数组名可以看做一个常量指针
- 数组名“指向”的是内存中数组的首元素的起始位置
- 数组名不包含数组的长度信息。
- 在表达式中数组名只能作为右值使用
- 只有在下列场合中数组名不能看做常量指针
——数组名作为sizeof操作符的参数
——数组名作为&运算符的参数
示例:
#include <stdio.h>
int main()
{
int a[5] = {0};
int b[2];
int* p = NULL;
p = a;
printf("a = %p\n", a);
printf("&a = %p\n",&a);
printf("p = %p\n", p);
printf("&p = %p\n", &p);
printf("sizeof(a) = %d\n", sizeof(a));
printf("sizeof(p) = %d\n", sizeof(p));
printf("\n");
p = b;
printf("b = %p\n", b);
printf("p = %p\n", p);
printf("&p = %p\n", &p);
printf("sizeof(b) = %d\n", sizeof(b));
printf("sizeof(p) = %d\n", sizeof(p));
//b = a;
return 0;
}
tips
可以发现,数组名的大小是数组的大小,而指针的大小就是这个指针变量的大小(64位系统占8个字节)
数组小结
- 数组是一片连续的内存空间
- 数组的地址和数组首元素的地址意义不同
- 数组名在大多数情况下被当成常量指针处理
- 数组名起始并不是指针,不能将其等同于指针。
指针和数组的分析(上)
数组的本质
- 数组是一段连续的空间
- 数组的空间大小为sizeof(array_type)*array_size
- 数组名可以看做指向数组第一个元素的常量指针
指针的运算
指针是一种特殊的变量,与整数的运算规则为:
p+n;<-->(unsigned int)p+n*sizeof(*p)
结论
当指针p指向一个同类型的数组的元素时:p+1将指向当前元素的下一个元素;p-1将指向当前元素的上一个元素。
指针的运算
- 指针之间只支持减法运算
- 参与剑法运算的指针类型必须相同
p1-p2;<——>((unsigned int)p1-(unsigend int)p2)/sizoef(type)
tips:
只有当两个指针指向同一个数组中的元素时,指针相减才有意义,其意义为指针所指元素的下标差。
当两个指针指向的元素不在同一个数组中时,结果未定义。
示例:
#include <stdio.h>
int main()
{
char s1[] = {'H', 'e', 'l', 'l', 'o'};
int i = 0;
char s2[] = {'W', 'o', 'r', 'l', 'd'};
char* p0 = s1;
char* p1 = &s1[3];
char* p2 = s2;
int* p = &i;
printf("%d\n", p0 - p1);
printf("%d\n", p0 + p2);
printf("%d\n", p0 - p2);
printf("%d\n", p0 - p);
printf("%d\n", p0 * p2);
printf("%d\n", p0 / p2);
return 0;
}
警告的两句的运行结果:
指针的比较
- 指针也可以进行关系运算
- 指针关系运算的前提是同时指向同一个数组中的元素
- 任意两个指针之间的比较运算(==,!=)无限制
- 参与比较运算的指针类型必须相同
#include <stdio.h>
#define DIM(a) (sizeof(a) / sizeof(*a))
int main()
{
char s[] = {'H', 'e', 'l', 'l', 'o','\0'};
char *s1="hello";
char* pBegin = s;
char* pEnd = s + DIM(s); // Key point
char* p = NULL;
printf("pBegin = %p\n", pBegin);
printf("pEnd = %p\n", pEnd);
printf("s=%s\n",s);
printf("Size: %d\n", pEnd - pBegin);
for(p=pBegin; p<pEnd; p++)
{
printf("%c\n", *p);
}
printf("\n");
return 0;
}
小结
- 数组声明时编译器自动分配一片连续的内存空间
- 指针声明时只分配了用于容纳地址值的4或8字节空间(看是32位还是64位系统)
- 指针和整数可以进行运算,其结果为指针
- 指针之间只支持减法运算,其结果为数组元素下标差
- 指针之间支持比较运算,其类型必须相同
指针和数组分析(下)
数组的访问方式
效率比较:
- 指针以固定的增量在数组中移动时,效率高于下标形式
- 指针增量为1且硬件具有硬件增量模型时,效率更高
- 下标形式于指针形式的相互转换:
tips:
下标形式的可读性高,指针形式效率稍高
示例:
#include <stdio.h>
int main()
{
int a[5] = {0};
int* p = a;
int i = 0;
for(i=0; i<5; i++)
{
p[i] = i + 1;
}
for(i=0; i<5; i++)
{
printf("a[%d] = %d\n", i, *(a + i));
}
printf("\n");
for(i=0; i<5; i++)
{
i[a] = i + 10;
}
for(i=0; i<5; i++)
{
printf("p[%d] = %d\n", i, p[i]);
}
return 0;
}
指针和数组不同:
#include <stdio.h>
int main()
{
extern int a[]; //定义的是数组
printf("&a = %p\n", &a);
printf("sizeof(&a) = %d\n", sizeof(&a));
printf("a = %p\n", a);
printf("*a = %d\n", *a);
return 0;
}
而如果改为指针类型
#include <stdio.h>
int main()
{
extern int* a;
printf("&a = %p\n", &a);
printf("sizeof(&a) = %d\n", sizeof(&a));
printf("a = %p\n", a);
printf("*a = %d\n", *a);
return 0;
}
则:运行结果有误
a和&a的区别
- a为数组的首元素的地址
- &a为整个数组的地址
- a和&a的区别在于指针运算
#include <stdio.h>
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int* p1 = (int*)(&a + 1);
int* p2 = (int*)((int)a + 1);
int* p3 = (int*)(a + 1);
printf("%d, \n", p1[-1]);
//printf("%d, %\n", p2[0]);
printf("%d, \n", p3[1]);
return 0;
}
数组参数
数组作为函数参数时,编译器将其编译成对应的指针
void f(int a[]);<——>void f(int* a);
void f(int a[5]);<——>void f(int* a);
tips:
一般情况下,当定义的函数中有数组参数时,需要定义另一个参数来标示数组的大小。
示例:
#include <stdio.h>
void func1(char a[5])
{
printf("In func1: sizeof(a) = %d\n", sizeof(a));
*a = 'a';
a = NULL;
}
void func2(char b[])
{
printf("In func2: sizeof(b) = %d\n", sizeof(b));
*b = 'b';
b = NULL;
}
int main()
{
char array[10] = {0};
func1(array);
printf("array[0] = %c\n", array[0]);
func2(array);
printf("array[0] = %c\n", array[0]);
return 0;
}
小结
- 数组名和指针仅使用方式相同
——数组名的本质不是指针
——指针的本质不是数组 - 数组名并不是数组的地址,而是数组首元素的地址
- 函数的数组参数退化为指针
C语言中的字符串
字符串的概念
- 字符串是有序字符的集合
- 字符串是程序中的基本元素之一
- C语言中没有字符串的概念
——C语言通过特殊的字符数组模拟字符串
——C语言的字符串是以==’\0’==结尾的字符数组
字符数组与字符串
- 在C语言中,双引号引用的单个或多个字符是一种的特殊的字面量
——存储于程序的全局只读存储区
——本质为字符数组,编译器自动在结尾加上’\0’
代码示例:
#include <stdio.h>
int main()
{
char ca[] = {'H', 'e', 'l', 'l', 'o'};
char sa[] = {'W', 'o', 'r', 'l', 'd', '\0'};
char ss[] = "Hello world!";
char* str = "Hello world!";
printf("%s\n", ca);
printf("%s\n", sa);
printf("%s\n", ss);
printf("%s\n", str);
return 0;
}
运行结果:
tips
字符串的打印是以’\0’为结果的。
字符串的小秘密
- 字符串字面量的本质是一个数组
- 字符串的字面量可以看作常量指针
- 字符串字面量中的字符不可改变
- 字符串字面量至少包含一个字符
字符串字面量
“hello world!”是一个无名的字符数组。
使用示例:
#include <stdio.h>
int main()
{
char b = "abc"[0];
char c = *("123" + 1);
char t = *"";
printf("%c\n", b);
printf("%c\n", c);
printf("%d\n", t);
printf("%s\n", "Hello");
printf("%p\n", "World");
return 0;
}
字符串的长度
- 字符串的长度就是字符串所包含的字符的个数
- 字符串长度指的是第一个‘\0’字符前出现的字符个数
- 通过’\0’结束符来确定字符串的长度
- 函数strlen用于返回字符串的长度。
#include <stdio.h>
#include <string.h>
int main()
{
char s[] = "Hello\0world";
int i = 0;
for(i=0; i<sizeof(s)/sizeof(char); i++)
{
printf("%c\n", s[i]);
}
printf("%s\n", s);
printf( "%d\n", strlen(s) );
printf( "%d\n", strlen("123") );
return 0;
}
自己的递归实现strlen函数
int my_strlen(char *str)
{
static int length =0;
if(str==NULL)
return 0;
else if(*str!='\0')
{
length++;
my_strlen(str+1);
}
return length;
}
int main()
{
char s[] = "Hello\0world";
printf( "%d\n", my_strlen(s) );
return 0;
}
字符串小结
- C语言中通过字符数组模拟字符串
- C语言中的字符串使用==’\0’作为结束符==
- 字符串字面量的本质为字符数组
- 字符串相关函数都依赖于结束符’\0’