二十一.指针基础
指针本质:指针本质上也是一个变量,需要占用一定的内存空间,指针用于保存内存地址的值。
传值调用与传址调用:1.指针是变量,因此可以声明指针参数
2.在一个函数体内部需要改变实参的值,则需要使用指针参数,传址。
3.函数调用时,实参值将复制到形参
4.指针适用于复杂类型作为参数的函数中(减少开销)
注:在c++中,在函数参数可以直接使用传址调用(foo(&a,&b)),不用指针;c中要实现传址调用,只有使用指针 (foo(*a,*b))。
const与指针:
二十二.数组基础
数组地址与数组名
1.数组名代表数组首元素的地址
2.数组的地址需要用取地址符&才能得到
3.数组首元素的地址值与数组的地址值相同
4.数组首元素的地址与数组的地址是两个不同的概念
//例如:a[5],数组名是a,数组元素首地址&a[0],数组地址是&a
数组名:1.数组名可以看做一个常量指针
2.数组名“指向“的是内存中数组首元素的起始位置
3.数组名只能作为右值使用
只有以下场合数组名不能看做常量指针:
1.数组名作为sizeof操作符参数
int a[5];
sizeof(a) = 20;
sizeof(a+0)=4;
sizeof(&a)=4;
sizeof(a+1)=4;
sizeof(*a) = 4;
只有当数组名作为sizeof参数的时候才是对整个数组求占用内存长度。
2.数组名作为&运算符的参数 ,&a=a=&(a+0);
常见错误:定义为指针,声明为数组
#include <stdio.h>
// another file
// char* p = "Hello World!";
extern char p[];
int main()
{
printf("%s\n", p);
return 0;
}
原因是:编译器处理指针是一次寻址,要通过p来寻址,p的地址和字符串地址分离,
处理数组无寻址,数组名p的地址就代表数组地址
修正版:
#include <stdio.h>
// another file
// char* p = "Hello World!";
extern char p[];
int main()
{
printf("%s\n", (char*)(*((unsigned int*)p)));
return 0;
}
在这个文件中p被误认为数组,强制类型转化为unsigned int类型的指针(unsigned int*)p,然后将p所代表的地址取出*((unsigned int*)p),然后将它作为指针输出
(char*)(*((unsigned int*)p)),结果就是正确的。
数组小结: 1.数组是一片连续的内存空间
2.数组的地址和数组首元素的地址意义不同
3.数组名在大多数情况下被当成常量指针处理
4.数组名其实并不是指针,在外部声明时不能混淆
二十三.数组与指针分析
指针是一种特殊变量,与整数的运算规则为:
p + n;<---------->(unsigned int)p+n*sizeof(*p);
注意:当指针指向一个同类型的数组元素时,p+1将指向当前元素的下一个元素;p-1将指向当前元素的上一个元素。
指针之间只支持减法运算,且必须参与运算的指针类型必须相同。
p1 - p2;<-------->((unsigned int)p1 - (unsigned int)p2)/sizeof(type);
注意:1.只有当两个指针指向同一个数组中的元素时,指针相减才有意义,其意义为指针所指元素的下标差
2.当两个指针所指向的元素不在同一个数组中时,结果未定义(但是可以计算,前提是类型相同)
指针的比较:可以进行关系运算<, <=, >, >=
注意:1.指针关系运算的前提是同时指向同一个数组中的元素
2.任意两个指针之间的比较运算(== !=)无限制(可以不是同一个数组,因为只是比较地址是否相等)
指针形式和下表形式访问数组: 理论上指针形式效率更高,现代编译器代码优化率已经优化,在固定增量时,效率已经相当。
a和&a的区别:
a为数组是数组首元素的地址
&a为整个数组的地址
a和&a的意义不同其区别在于指针运算
a+1--->(unsigned int)a + sizeof(*a)
&a+1---->(unsigned int)(&a) + sizeof(*&a)=(unsigend int)(&a) + sizeof(a)
求数组长度:length = sizeof(a)/sizeof(a[0])
#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, %d, %d\n", p1[-1], p2[0], p3[1]);
return 0;
}
输出:p1[-1]<===> a[4]=5 p2[0]<===>0x02000000(小端) p3[1]<===>a[2]=3
数组参数:c语言中,数组作为函数参数时,编译器将其编译成对应的指针
void f(int a[]); <===>void f(int* a);
void f(int a[5]);<===>void f(int* a);
结论:一般情况下,当定义的函数中有数组参数时,需要定义另一个参数来标示数组的大小
指针和数组的对比:
1.数组声明时编译器自动分配一片连续内存空间
2.指针声明时只分配了用于容纳指针的4字节空间
3.在作为函数参数时,数组参数和指针参数等价
4.数组名在多数情况下可以看做常量指针,其值不能改变
5.指针的本质是变量,保存的值被看做内存中的地址
二十四.c语言中的字符串
从概念上讲,c语言中没有字符串数据类型,使用字符数组来模拟字符串,c语言中的字符串是以'\0'结束的字符数组
c语言中的字符串可以分配于栈空间,堆空间或者只读存储区
#include <stdio.h>
#include <malloc.h>
int main()
{
char s1[] = {'H', 'e', 'l', 'l', 'o'};//不是字符串
char s2[] = {'H', 'e', 'l', 'l', 'o', '\0'};\\\栈中的字符串
char* s3 = "Hello";\\只读存储区
char* s4 = (char*)malloc(6*sizeof(char));\\堆中的字符串
s4[0] = 'H';
s4[1] = 'e';
s4[2] = 'l';
s4[3] = 'l';
s4[4] = 'o';
s4[5] = '\0';
free(s4);
return 0;
}
字符串的长度:指的是字符串所包含字符的个数,第一个'\0'字符前出现的字符个数,c语言中通过'\0'结束符来确定字符串长度。
strlen库函数就是基于这个标准来计算字符串长度。
#include<stdio.h>
#include<string.h>
char g[100];
int main()
{
g[0] = 'H';
g[1] = 'e';
g[2] = 'l';
g[3] = 'l';
g[4] = 'o';
g[5] = '\0';
g[6] = 'H';
g[7] = 'e';
g[8] = 'l';
g[9] = 'l';
g[10] = 'o';
printf("length = %d\n",strlen(g));
printf("size = %d\n",sizeof(g));
return 0;
}
输出为:length = 5,size = 100;这就是字符串和字符数组的区别。
关于strlen的警告:strlen的返回值是无符号数定义的,因此详见不可能产生负数(总是大于0),下面的语句不等价:
char* a = "123";
char* b = "1234";
if(strlen(a) >= strlen(b))
{
……
}
if(strlen(a) - strlen(b) >= 0)
{
……
}
一句话实现strlen:
#include <stdio.h>
#include <assert.h>
size_t strlen(const char* s)
{
return ( assert(s), (*s ? (strlen(s+1) + 1) : 0) );
}
int main()
{
printf("%d\n", strlen( NULL));
return 0;
}
不受限制的字符串函数:通过寻找字符串的结束符'\0',来判断长度
字符串复制:char* strcpy(char* dst, const char* src);
字符串连接:char* strcat(char* dst,const char* src);
字符串比较:int strcmp(const char* s1,const char* s2);
注意:输入参数中必须包含'\0'。
strcpy和strcat必须保证目标字符数组的剩余空间足够保存整个源字符串
strcmp以0表示两个字符串相等,第一个大于第二个返回大于0,第一个小于第二个返回小于0
strcmp不会修改参数值 ,检测'\0'为结束符
实现strcpy函数:
#include <stdio.h>
#include <assert.h>
char* strcpy(char* dst, const char* src)
{
char* ret = dst;
assert(dst && src);//判断是否为空
while( (*dst++ = *src++) != '\0' );
return ret;
}
int main()
{
char dst[20];
printf("%s\n", strcpy(dst, "Delphi Tang!"));
return 0;
}
长度受限的字符串函数:
字符串复制:char* strncpy(char* dst,const char* src,size_t len);
字符串连接:char* strncat(char* dst,const char* src,size_t len);
字符串比较:char* strncmp(const char* s1,const char* s2,size_t len);
注意事项:strncpy:只复制len个字符到目标字符串
1.源字符串的长度小于len时,剩余空间以'\0'填充。
2.当源字符串的长度大于len时,只有len个字符串会被复制,且它将不会以'\0'结束。
strncat:最多从源字符串中复制len个字符到目标字符串中
1.总是在结果字符串后面添加 ‘\0’
2.不会用'\0'填充目标串中的剩余空间
strncmp:只比较len个字符是否相等