地址只是一个点,没有长度的(默认长度是一个字节)
数组中几个关键符号(a a[0] &a &a[0])的理解
(1)a 就是数组名。a做左值时表示整个数组的所有空间,又因为C语言规定数组操作时要独立单个操作,不能整体操作数组, 所以a不能做左值;
a做右值表示数组首元素(数组的第一个元素,也就是a[0]的首地址)(首地址就是起始地址,就是4个字节中最开始第一个字节的地址).a做右值等同于&a[0];
(2)a[0]表示数组的首元素,也就是数组的第0个元素。做左值时表示数组第0个元素对应的内存空间连续(连续4字节);做右值时表示数组第0个元素的值(也就是数组第0个元素对应的内存空间中存储的那个数)
(3)&a就是数组名a取地址,字面意思来看就是数组的地址。&a不能做左值(&a实质是一个常量)
&a做右值时表示整个数组的首地址
总结
1.&a和a做右值时区别:&a是整个数组的首地址,a表示首元素首地址。这2个在数组上是相等的,但是意义不相同,导致他们在参与运算时候有不同的表现。
2.a和&a[0]做右值时意义和数值相同可以互相替代。
3.&a是常量,不能做左值
4.a做左值代表真个数组所有空间,不能做左值
数组和指针的天生姻缘
以指针的方式来访问数组元素
#include<stdio.h>
int main(void)
{
int a[5] = {1,2,3,4,5};
printf("a[3] = %d.\n,a[3]");
printf("*(a+3) = %d.\n,*(a+3)");
return 0;
}
#include<stdio.h>
int main(void)
{
int a[5] = {1,2,3,4,5};
int *p;
p = &a[2];
printf("*(a+1) = %d.\n,*(a+1)");
return 0;
}
(1)数组元素使用时不能整体访问,只能单个访问。访问的方式有2种:数组形式和指针形式。
(2)数组格式访问数组元素是:数组名[下标];(注意下标从0开始)
(3)指针格式访问数组元素是:*(指针+偏移量);
如果指针是数组首元素地址(a或者&a[0]),那么偏移量就是下标;指针也可以不是首元素地址,而是其他那个元素地址,这时候偏移量就要考虑叠加了。
(4)数组下标方式和指针方式均可以访问数组元素,两者的实质是一样的,编译器内部都是用指针方式来访问数组元素的,用指针方式访问数组才是本质
从内存角度理解指针访问数组的实质
指针和数组类型的匹配问题
(1)int *p; int a[5]; p = a; //类型匹配
(2)int *p; int a[5]; p = &a; //类型不匹配,p是int
* , &a是整个数组的指针,也就是一个数组指针类型,不是int指针类型,所以不匹配
(3)a和&a[0]是元素的指针,也就是int *类型;而&a是数组指针,是int(*)[5];类型。
总结:指针类型决定了指针如何参与运算
(1)指针参与运算时,因为指针变量本身存储的数值是表示地址的,所以运算也是地址运算。
(2)指针参与运算的特点是,指针变量+1,并不是真的加1,而是加1*sizeof(指针类型);如果是int *指针,则+1就实际表示地址+4,如果是char * 指针,则+1就表示地址+1;如果是double* 指针,则+1就表示地址+8.
(3)指针变量+1时实际不是加1而是加1*sizeof(指针类型),主要是因为是希望指针+1后刚好指向下一个元素(而不希望错位)。
指针与强制类型转换
变量的数据类型的含义
(1)所有的类型的数据存储在内存中,都是按照二进制格式存储的。所以内存中只知道有0和1,不知道是int的,还是float的还是其他的类型数据
(2)int char short等属于整型,他们的存储方式(数转换成二进制往内存中放的方式)是相同的,只是内存格子大小不同(所以这几种整型就彼此叫二进制兼容格式);而float和double的存储方式彼此不同,和整型更不同。
(3)存进去时是按照这个变量本身的数据类型存储的,但是取出时是按照printf中%d之类的格式化字符串的格式来提取的。此时虽然a所代表的内存空间中的10101序列并没有变(内存是没被修改的)但是怎么理解(怎么把这些1010转成数字就变了)
总结:C语言中的数据类型的本质,就是决定了这个数在内存中怎么存储的问题,也就是决定了怎么解析。所以要求我们平时数据类型不能胡乱瞎搞。
分析几个题目:
* 按照int类型存却按照float类型取 一定会出错
* 按照int类型存却按照char类型取 可能会出错
* 按照short类型存却按照int类型取 可能会出错
* 按照float类型存却按照double类型取 一定会出错
指针的数据类型的含义
(1)指针的本质是变量
(2)一个指针涉及2个变量:一个指针变量自己本身,一个是指针变量指向的变量
(3) int *p:定义指针变量时,p(指针变量本身)是int *类型,*p(指针指向的变量)int类型
(4)int * 类型就是指针类型,只要是指针类型就是占4个字节,解析方式按照地址方式解析的(不管是int * 还是char*还是double*)的解析方式是相同的,都是地址。
(5)对于指针所指向的那个变量,指针的类型就很重要,指针所指向的那个变量的类型(他所对应的内存空间的解析方法)要取决于指针类型。譬如指针是int *的,那么指针指向的变量就是int 类型。
指针数据类型转换实例分析1
(1)int 和 char 类型是整型,类型兼容的。所以互转的时候有错有对。
(2)int 和 char 的不同在于chai只有一个字节而int有4个字节,所以int的范围比char大。在char所表示的范围之内int和char是可以转换不会出错;但是超过了char的范围后char转成int不会错,而int到char会错(大到小会错)
指针数据类型转换实例分析2
sizeof运算符
(1)sizeof是C语言的一个运算符(主要sizeof不是函数,虽然用法很像函数),sizeof的作用是用来返回()里面的变量或者数据类型占用的内存字节数。
(2)sizeof存在的加值?主要是因为在不同平台下各种数据类型所占的内存字节数不尽相同,所以程序中需要使用sizeof判断当前变量在当前环境下占几个字节。
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[] = "hello";
printf("sizeof(str) = %d.\n",sizeof(str)); //字符串会以\0做结束标志
printf("sizeof(str[0]) = %d.\n",sizeof(str[0]));
printf("strlen(str) = %d.\n",strlen(str)); //srtlen 用来得到一个字符
串的长度但是不算\0
return 0;
}
#include <stdio.h>
#include <string.h> //32位系统中所有类型指针的长度都是4
int main(void)
{
char str[] = "hello";
char *p = str;
printf("sizeof(p) = %d.\n",sizeof(p)); //4相当于sizeof(char *)
printf("sizeof(*p) = %d.\n",sizeof(*p)); //1相当于sizeof(char)
printf("strlen(p) = %d.\n",strlen(p)); //5相当于strlen(str)
return 0;
}
#include <stdio.h>
int main(void)
{
int b[100] = 10;
printf("sizeof(b) = %d.\n",sizeof(b));
return 0;
}
//size(数组名)的时候,数组名不做左值也不做右值,纯粹就是数组名的含义。那么sizeof(数组名)实际返回的是整个数组所占用内存空间(以字节为单位的)
(1)函数传参,形参是可以用数组的
(2)函数形参是数组时,实际传递不是整个数组,而是数组的首元素首地址。也就是数函数传参用数组来传,实际相当于传递的是指针(指针指向数组首元素首地址)。
#include <stdio.h>
#include <string.h>
#define dpChar char *
typedef char * tpChar; //重命名类型 换名字
int main(void)
{
dpChar p1, p2; //char *p1, char p2
tpChar p3, p4; //char *p3, char *p4
printf("sizeof(p1) = %d.\n",sizeof(p1));
printf("sizeof(p2) = %d.\n",sizeof(p2));
printf("sizeof(p3) = %d.\n",sizeof(p3));
printf("sizeof(p4) = %d.\n",sizeof(p4));
}
指针和函数传参
普通变量作为函数传参
#include<stdio.h>
//&a和&b不同,说明a和b不是同一个变量(在内存中a和b是独立的2个内存空间)
//但是a和b是有关联的,实际上b是a赋值得到的。
void func1(int b)
{
//在函数内部,形参b的值等于实参a
printf("b = %d.\n",b);
printf("in func1,&b = %p.\n",&b);
}
int main(void)
{
int a = 4;
printf("&a = %p.\n",&a);
func1(a);
return 0;
}
(1)函数传参时,普通变量作为参数时,形参和实参名字可以相同也可以不同,实际上都是用实参来代替对应的形参)
(2)在子函数内部,形参的值等于实参。原因是函数调用时把实参的值赋值给了形参。
(3)这就是很多书上写到“传值调用”。
数组作为函数形参
(1)函数名作为函数形参时,实际传递不是整个数组,而是数组的首元素的首地址(也就是整个数组的首地址。因为传参时是传值,所以这2个没区别)。所以在子函数内部,传进来的数组名就等于是一个指向数组首元素首地址的指针。所以sizeof得到的是4。
(2)在子函数内传参得到的数组首元素首地址,和外面得到的数组首元素首地址的值是相同的。很多人把这种特性叫做“传址调用”(所谓的传址调用就是调用子函数时传了地址(也就是指针),此时可以通过传进去地址来访问实参。)
(3)数组作为函数形参时,【】里的数字是可有可无的。为什么?因为数组名做形参传递的实际只是个指针,根本没有数组长度信息。
指针作为函数形参
(1)只有一句话:和数组作为函数形参是一样的。
结构体变量作为形参
#include<stdio.h>
struct A
{
char a;
int b;
};
void fun4(struct A a1)
{
printf("sizeof(a1) = %d.\n",sizeof(a1));
printf("&a1 = %p.\n", &a1);
printf("a1.b = %d.\n",a1);
}
int main(void)
{
struct A a =
{ .a = 4, //结构体赋值 加, (逗号不是分号)
.b = 5555,
};
printf("sizeof(a) = %d.\n",sizeof(a));
printf("&a = %p.\n", &a);
printf("a.b = %d.\n",a);
func4(a);
return 0;
}
(1)结构体变量作为函数形参的时候,实际上和我们普通变量一模一样,所以结构体变量其实也是普通变量而已
(2)因为结构体一般都很大,所以如果直接用结构体变量进行传参,那么函数调用效率就会降低,因此可以通关改传变量的指针进去。
传址调用与传值调用
#include<stdio.h>
void swap1(int a,int b)
{
int tmp;
tmp = a;
a = b;
b = tmp;
printf("a = %d,b = %d.\n",a,b);
}
void swap2(int *a,int *b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
printf("*a = %d,*b = %d.\n",*a,*b);
}
int main(void)
{
int x = 3,y = 5;
swap1(x,y);
printf("x = %d,y = %d",x,y); //x = 5,y = 3 交换成功
swap2(&x,&y);
printf("x = %d,y = %d",x,y); //x = 3,y = 5 没有交换
}
(1)传值调用描述的是这样一种现象:x和y作为实参,自己并没有真身进入wsap1函数内部,而只是拷贝了一份自己的副本进入子函数swap1,然后我们在子函数swap1中交换的实际是副本而不是x,y真生。所以在swap1内部确实是交换了,但外部的x和y根本没有受到影响。
(2)在swap2中x和y真的被改变了。但是x,y真身没有进入,单swap2我们把x和y的地址传到子函数了,于是在子函数可以通过指针解引方式从函数内部访问到外部的x和y真身,从而改变x和y。