C——提高(1)
2015-12-01 23:10
260 查看
一、数组做函数参数退化问题
#include <stdio.h> //void printfArr(int a[6], int num){ //void printfArr(int a[], int num){ void printfArr(int *a, int num){// 三种写法结果是一样的 int i; int num2 = 0; num2 = sizeof(a) / sizeof(a[0]); printf("num2 = %d\n", num2); for (i = 0; i < num; i++){ printf("%d ", a[i]); } } int main() { int num1 = 0; int arr[] = {12,34,1,3,45,23}; num1 = sizeof(arr) / sizeof(arr[0]); printf("num1 = %d\n", num1); printfArr(arr, num1); system("pause"); }
结论:
数组做函数参数会退化为一个指针。也就是说形参中的数组编译器会把它当成指针处理。实参arr和形参a的数据类型本质不一样,所以sizeof的结果不一样。
遍历数组需要将数组地址连同数组长度一起作为入参传进去。
形参写在函数上和写在函数内作用是一样的,只不过写在函数上具有对外的属性而已(也就是说可以通过实参传给形参的方式来初始化它)。
二、数组名与指针探讨数据类型本质
int main(){ int arr[] = {12,34,1,3,5}; int *p = &arr; printf("arr = %d\n", arr); printf("arr+1 = %d\n", arr + 1); printf("&arr = %d, p=%d\n", &arr, p); printf("&arr+1 = %d, p+1=%d\n", &arr + 1, p+1); system("pause"); return 0; }
结论:
arr代表数组首元素的地址。
&arr代表整个数组的地址。
arr+1 与 &arr+1 的结果不一样,是因为arr与&arr所代表的数据类型不一样。导致指针的步长不一样。
c/c++编译器把arr处理为一个常量指针,arr = arr+1会报错,p = p+1则不会报错。
经典总结:数据类型的本质就是固定大小内存块的别名。
数据类型是C/C++编译器为了方便的表达现实生活中的人事物而引入的一个概念。
三、变量的本质
经典总结:变量的本质就是(一段连续)内存空间的别名(是一段内存空间的编号,类似于一个门牌号)。既然数据类型和变量都是内存块的别名,这段内存块就可以有多个别名,那么如何增加新的别名呢?如下:
(1)、对数据类型取别名:
typedef int INTEGER INTEGER a = 10; // int、INTEGER都是int所代表的的数据类型的别名
typedef struct Teacher { char name[64]; int age; } Teacher; Teacher t1;
(2)、对内存空间取别名:
引用就是某一变量(目标)的内存空间的一个别名,对引用的操作与对变量直接操作完全一样。
引用的声明方法:类型标识符 &引用名=目标变量名;
int a; // 假如变量a指向的int型内存空间首地址为0x36345 int &ra=a; //定义引用ra,它是变量a的引用
以0x36345开始的4个字节的这个内存地址,刚才他有个别名a,现在又有个别名ra了。
说明:
(1)&在此不是求地址运算,而是起标识作用。
(2)类型标识符是指目标变量的类型。
(3)声明引用时,必须同时对其进行初始化。
(4)引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。
ra=1; 等价于 a=1;
(5)声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。&ra与&a相等。
(6)不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名。
四、内存四区
栈的属性和buf地址增长方向是两个不同的概念
一般情况下:栈的生长方向开口向下,堆的生长方向开口向上
void main() { char buf[128]; // 静态绑定的时候,buf代表的内存空间的标号就已经定义下来了 }
不管栈开口向上还是向下,buf内存地址永远向上的。
五、函数调用模型
六、指针专题大纲
铁律1: 指针也是一种数据类型
(1)、指针变量和它指向的内存块是两个不同的概念int *p = &a;
a、给p赋值p=0x12345; p = p+1; 只会改变指针变量的值,不会改变所指向的内存块的值。
b、给*p赋值*p=1111; 不会改变指针变量的值,只会改变所指向的内存块的值。
c、*p放在等号左边——>给内存赋值 ——> 保证所指向的内存块能修改
d、*p放在等号右边——>从内存获取值
(2)、指针也是一种数据类型,是指它指向的内存空间的数据类型
指针可以看做是一种复合数据类型,依附于它所指向的内存空间的数据类型的一种数据类型。
指针步长(p++),根据所指内存空间的数据类型来确定。
如:
intarr[4] = {2,3,4,5}; arr++; // 指向数组首元素的地址,该地址段代表的数据类型是int类型,所以步长是4 &arr++;// 指向整个数组的地址,该地址段代表的数据类型是数组类型,所以步长是4*4=16
经典总结:
指针指向谁,就把谁的地址赋值给指针。
指针的数据类型决定了指针的步长。
(3)野指针产生的原因
指针变量和它所指向的内存空间变量是两个不同的概念。
释放了指针所指的内存空间, 但是指针变量本身没有重置为null,造成释放的时候出现野指针。
char * p = NULL; p = (char *)malloc(100); if (p ==NULL){ return; } if (p !=NULL){ free(p);// 第一次free没有问题,释放了指针p所指向的内存空间 // p = NULL; 为避免野指针,此处应置为NULL } if (p !=NULL){// 指针变量p还是指向之前那块内存空间,此时p变成了野指针 free(p);// 释放未知的内存空间,出错 }
避免方法:定义指针的时候,初始化为null,释放指针所指的内存空间后,重置指针变量为null。
铁律2: 间接赋值(*p)是指针存在的最大意义
函数调用时,用实参取地址传递给形参,在被调用函数里面用*p来改变实参,这样就相当于把运行结果传递出来了。通过把内存的首地址以形参的方式传递过去,实现不同的函数可以同时操作一块内存空间。
间接赋值是指针做函数参数的精华,主函数和被调函数直接通过内存交换运算结果。
参考示例:
#define _CRT_SECURE_NO_WARNINGS #include "stdio.h" #include "stdlib.h" #include "string.h" // 指针做函数参数,使用输入输出特性的 int getMem(char **param1/*out*/, int *length1/*out*/, char **param2/*out*/, int *length2/*out*/) { int result = 0; int mLen1, mLen2; char *mP1, *mP2; mP1 = (char *)malloc(100); strcpy(mP1, "123456"); // 通过形参改变实参的值 *length1 = strlen(mP1);// 一级指针 间接赋值 *param1 = mP1;// 二级指针 间接赋值, mP2 = (char *)malloc(100); strcpy(mP2, "abcde"); *length2 = strlen(mP2); *param2 = mP2; return result; } int main() { char *p1 = NULL; int len1 = 0; char *p2 = NULL; int len2 = 0; int result = getMem(&p1, &len1,&p2, &len2);// 通过函数调用,求得四个实参的值 if (result != 0) { printf("fun getMem() error:%d", result); } printf("p1=%s \n", p1); printf("len1=%d \n", len1); printf("p2=%s \n", p2); printf("len2=%d \n", len2); // 释放堆内存 if (p1 != NULL) { free(p1); p1 = NULL; } if (p2 != NULL) { free(p2); p2 = NULL; } system("pause"); return 0; }
运行结果:
间接赋值推论
在函数调用时:
用1级指针形参,去间接修改了0级指针(实参)的值。
用2级指针形参,去间接修改了1级指针(实参)的值。
用n级指针形参,去间接修改了n-1级指针(实参)的值。
铁律3: 理解指针必须和内存四区概念相结合
(1) 主调函数可以把堆区、栈区、全局数据区内存地址传递给被调函数。(2)被调函数只能返回堆区、全局区数据。
(3)指针做函数参数,是有输入输出特性的。
相关文章推荐
- SpringMVC_参数绑定1
- Android Speex编译及使用
- 理解C语言——从小菜到大神的晋级之路(1)——引言:C语言的前世今生
- 局域网 和 广域网(内网和外网)
- 内存管理算法--Buddy伙伴算法
- java如何快速实现深copy
- springmvc源码心得之handler及handlerAdapter设计
- LeetCode Flatten Binary Tree to Linked List
- node.js初学(一)
- poj 3311 floyd + 状态
- 小试Unity中OBJ和Scene打包Bundle与加载--wondows平台下
- Jquery为DIV添加点击事件,Jquery为a标签超链接添加点击事件
- ReentrantLock的使用和Condition
- 关于cssReset所需掌握的知识点(一)
- Linux下配置文件读取操作流程及其C代码实现
- 10013---ASM字节码框架
- 大批量、大体积excel文件转版为印刷版pdf:对excel文件格式要求及参数设置(2015/12/03)
- 机器码 与 汇编指令的关系
- Quartz2D初体验(二)
- Java查看动态代理生成的代码