您的位置:首页 > 其它

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)指针做函数参数,是有输入输出特性的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: