把数组、结构体等都看成是一种数据类型!

数组

首先来看看数组。这里,我把数组分成两类,一类是像(int,double,结构体等的)普通数组。一类是像(字符数组)这样另外的数组。

“普通数组”

int a[] = {1,2};   //未明确指定个数,可以通过后面给出的值的个数来默认。也就是说a[2]是乱码。
int b[100] = {1,2};  //指定100个,后面给的值是前两个,其余的98个赋值为0。
int c[200] = { 0 };  //整个数组全部赋值为0
cout << sizeof(c);  //猜猜这句话的输出是多少。是800!也就是说c代表了200个int
//好,下面是重点,我为什么说数组是一种类型呢?
//因为我们可以把这句int c[200] = { 0 };将int [200]看成是一种类型,而c是它定义的变量。
//那么上面的那个800就很好理解了。
 cout << c;  //我测试了,输出值是一个地址。(说明c本身是一个指针。)
//这也是为什么数组作为形参时可以“退化成”一个地址。
//并且这个地址是c[0]的地址。
//那么c+1的步长为4 
//&c+1的步长为200 * 4  是不是就很好理解了。
//下面是一层抽象化
    //定义一个数组数据类型
	typedef int (ARR_TYPE)[5];(关于typedef自己搜一下)
	//声明一个int [5] 的数组 
	ARR_TYPE myArray;
	for(int i = 0;i<5;i++)
	{
		myArray[i] = 100+i;
	}

//下一个问题:
    //定义数组指针,关于这句话的解释,首先,它是一个指针,其次,它指向一个数组。
	ARR_TYPE *p_arr;  //ARR_TYPE是前面定义的数组数据类型
	p_arr = &myArray;  //这是取数组名的地址,也即是第一个元素的地址的地址
	for(int i = 0;i<5;i++)
	{
		printf("%d  ", *(*p_arr+i));  //这里的解引用,*p_arr取到第一个元素的地址。
	}
	//直接定义一个数组指针数据类型
	typedef int (* P_ARR_TYPE)[5];
	P_ARR_TYPE p_arr_2 = &myArray;
	for(int i = 0;i<5;i++)
	{
		printf("%d  ", *(*p_arr_2+i));
	}
	//还可以直接定义数组指针变量 
	int (*my_p_arr)[5] = &myArray;
	printf("%d\n",*(*my_p_arr+2));

//下一个问题:二维数组
	int arr[3][3] = {
		{1,2,3},
		{4,5,6},
		{7,8,9}
	};
	int brr[][3] = {1,2,3,4,5,6,7,8,9};
	//这是两种常见的定义方法,需要特别指出的是二维数组的列数是必须要指定的。
    //void printArray(int (*myArray)[3],int row,int col)  
    //关于这个参数的书写,这两种都可以,其中共性就是列数必须要指明。
    void printArray(int myArray[][3], int row, int col)
    {
	    for(int i = 0;i<row;i++)
	    {
		    for(int j = 0;j<col;j++)
		    {
			    //printf("%d  ",*(*(myArray+i)+j));
			    printf("%d ",myArray[i][j]);
		    }
		    printf("\n");
	    }
    }
    int main()
    {
	    int arr[3][3] = {
		    {1,2,3},
		    {4,5,6},
		    {7,8,9}
	    };
	    printArray(arr,3,3);
	    return 0;	
    } 

关于“普通数组”,还要特别讲解一下关于结构体的数组。
首先是结构体的简单知识回顾。

struct Person{    //定义一个结构体数据类型。
	char name[64];
	int age;
};
struct Person p1 = {"小白",20};  //struct Person表示一个数据类型
struct Person2{   //定义一个结构体数据类型,并顺便定义一个变量。
	char name[64];
	int age;
}p2;
struct Person3{  //定义数据类型和变量,并顺便初始化。
	char name[64];
	int age;
}p3={"小红",18};

typedef struct Person4{   //定义别名。
	char name[64];
	int age;
}Person4;
//结构体的偏移量问题
    #include <stddef.h>  //offsetof
    struct A{
	    char a1;
	    int a2;
    };
    struct C{
	    int a;
	    double b;
    };
    struct B {
	    char a;
	    int b;
	    struct C c;
    };
    int main()
    {
	    struct B b = {'a',10,30,3.056};
	    cout << sizeof(struct C) << endl;  //输出是16
	    int off1 = offsetof(struct B,c);   //输出是8
	    int off2 = offsetof(struct C,b);     //8
	    printf("off1 = %d\n",off1);
	    printf("off2 = %d\n",off2);
	    //下面的利用偏移量的方法都可以正确的输出相应的值
	    printf("c.b = %f\n", *(double *)(((char*)&b+off1)+off2));
	    printf("c.b = %f\n",((struct C *)((char*)&b+off1))->b);
	    struct A a =  {'h',20};
	    printf("A a : a2 %d\n",offsetof(struct A,a2));
	    printf("a2= %d\n",a.a2);
	    printf("a2 = %d\n", *(int *)((char *)&a+offsetof(struct A,a2)));
	    printf("a2 = %d\n",*((int *)&a+1));
	    return 0;
    }
//这是关于结构体赋值的问题。
    //结构体可以直接赋值,比如p2=p1; p1和p2都是结构体
    //浅拷贝和深拷贝
    //浅拷贝就是直接数据的拷贝
    //深拷贝则是对地址的操作。
//来了,结构体指针
    Person4 p4 = {"小黑",22};
    Person4 *p = &p4;   //表示p存放的是p4的地址。那么p4的地址又是什么?
    cout << sizeof(Person4) << endl;  //输出68
    Person4 p4 = {"小黑",22};
    cout << sizeof(p4)<<endl;     //输出68
    cout << &p4 << endl;    //这个输出和下面的输出是一样的
    cout << &p4.name << endl;   //也就是说p4的地址是p4的第一个元素的地址。
    //这里注意将它和普通整型数组相区分。这个结构体数据类型可以类比于int
    printf("p->name:%s,p->age%d\n",p->name,p->age);
    //在堆上分配结构体变量的空间
    Person4 *q =  (Person4 *)malloc(sizeof(struct Person4));
    strcpy_s(q->name,"小紫");
    q->age = 19;
    printf("q->name:%s  , q->age:%d\n",q->name,q->age);
    free(q);
    q = NULL;
//下一个问题,结构体数组
    //在栈区分配内存
	struct Person persons[]	= {
		{"小白",20},
		{"小白2",22},
		{"小白3",23},
	};  //这里是结构体数组,这里把struct Person []看成是一种数据类型,persons是变量。
	cout << sizeof(persons) << endl;    //204
	cout << sizeof(struct Person) << endl;   //68
	cout << persons << endl;   //006FF6F8  //也就是数组名存放的是第一个元素的地址。
	//这里与一个普通的整型数组是一样的。
	cout << &persons[0] << endl;   //006FF6F8
	//下面是在堆区分配内存
	struct Person *pArr = (struct Person *) malloc(sizeof(struct Person)*3);
	for(int i = 0;i<3;i++)
	{
		sprintf_s(pArr[i].name,"小白_%d",i+1);  
		//sprintf()函数和printf()类似, 只是把输出发送到buffer(缓冲区)中.返回值是写入的字符数量.
		//并且这个函数可用于多个字符串有大量相似部分,只有一小部分不同的情况。
		//比如这里,我们分别将三个姓名初始化为小白_1,小白_2,小白_3.
		pArr[i].age = 18+i;
	}
	for(int i = 0;i<3;i++)
	{
		printf("%s,%d\n",pArr[i].name,pArr[i].age);
	}
	free(pArr);  //最后记得free
	pArr = NULL;
//最后一个问题,关于结构体的位域
	struct data2 {
	int a : 4;
	int : 4;  //表示空域,跳过这段空间 
	int b : 2;
    };
    struct A {
	int a : 5;
	int b : 3;
    };
    int main()
   {
	    char str[100] = "3134324324afsadfsd1fj1s5jf5";  //这个数组的每个元素是字符。
	    struct A d;
	    memcpy(&d, str, sizeof(struct A));
	    printf("d.a = %d\n", d.a);   //输出是-13
	    printf("d.b = %d\n", d.b);     //输出是1
	    //具体介绍一下怎么算的
	    //我使用的电脑是从低位开始存放数据的。因此,虽然传递了4个字节给d,但是因为
	    //使用了位域,因此,d实际使用的字节只有1个。‘3’字符的ASCII码为51,化成二进制为00110011
	    //实际上d.a的二进制为10011,d.b的二进制为001.并且他们是补码。(现在计算机一般都用补码)
	    //所以结果是-13和1.
	    printf("sizeof(struct A):%d\n", sizeof(struct A));
	    return 0;
    }
    struct data1 {
	    int a : 7;
	    int b : 3;//下一个单元开始存放
    };
    int main()
    {
	    struct example {
		    unsigned a : 1;
		    unsigned b : 3;
		    unsigned c : 4;
	    }bit, * pBit;
	    bit.a = 1;
	    bit.b = 7;
	    bit.c = 15;
	    pBit = &bit;
	    pBit->b &= 3;
	    pBit->c |= 1;
	    printf("pBit->b = %d, pBit->c = %d\n", bit.b, bit.c);  //结果为3和15
	    return 0;
    }

“另外的数组”

这里指的是字符数组。
上面例子中看到的那个数组char str[100] = "3134324324afsadfsd1fj1s5jf5"; //这个数组的每个元素是字符。,它和普通的数组不一样。我们知道普通的数组名代表的是数组首元素的地址。而这个数组不是这样的。请看:

int main() {
	char ch[100] = { '3','4','5','6','7','8','f' };
	cout << ch << endl;      //输出是345678f
	cout << &ch[0] << endl;    //输出是345678f
	cout << &ch[1] << endl;      //输出是45678f
	cout << &ch << endl;   //输出是009AFC74(本次运行)
	cout << (int *)&ch[0] << endl;     //输出是009AFC74,说明这个&ch代表的是第一个元素的地址
	//并且用这种方法才可以输出第一个元素的地址
	char str[100] = "3134324324afsadfsd1fj1s5jf5";  //这个数组的每个元素是字符。
	//通过调试VS发现,其实这个字符串的存储是分割成一个一个字符的。本质上和上面的字符数组一样。
	cout << str << endl;  //输出一整个字符串
	cout << &str[0] << endl;   //输出一整个字符串
	cout << &str << endl;  //输出地址,007FFBEC
	cout << (int*)&str[0] <<endl;  //输出地址,007FFBEC
	cout << str[0] << endl;   //输出3
	cout << str[99] << endl;  //输出是NULL
	cout << str[99]+1 << endl;  //输出是1 因为NULl的ASCII码为0
	return 0;
}
int trimSpace(char* inbuf/*in*/, char* outbuf/*out*/);
int main()
{
	char str[] = "   abcdefgdddd    ";  //存放在栈区
	//char* str = (char *)"   abcdefgdddd    ";//会报错,因为指向的内存是不可写的 存放在全局区
	char buf[1024] = {0};  //将buf初始化为NULL
	cout << buf[0]+1 << endl;  //输出1.证明确实是初始化为NULL,NULL的ASCII码为0
	int ret = 0;
	ret = trimSpace(str,buf); //前面没提到的字符数组的数组名是什么,&str代表的是首元素的地址。
	//但是这里对于字符串,数组名也可以直接这么写,按照这个函数调用的意思,数组名也表示首元素地址?
	//我只能存疑了。(苦笑)

指针步长

指针,听起来就头疼。不不不。听起来就很奇妙。(给自己积极的心理暗示)
看代码:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>   //前三行为常规框架
//指针步长
void test01()
{
	char* p1 = NULL;     //p1为指针,指向char类型的指针
	printf("%d\n", p1);   //这里的输出结果是0
	printf("%d\n", p1 + 1);   //来了来了,关键点,这里的输出是1

	int* p2 = NULL;    //p2是指向int类型的指针
	printf("%d\n", p2);  //这里的输出结果为0
	printf("%d\n", p2 + 1);    //这里的输出结果是4。
	//从这两段代码,我们可以知道指针的步长具体是多少了。
	//具体看其指向的数据类型占几个字节。
    //下面是进一步理解。
	char buf[100] = { 0 };  //定义数组并初始化为0
	int a = 10011;   //定义一个整型变量a,可以看出来它比较大,一个字节是放不下的
	memcpy(buf + 1, &a, sizeof(int));  //这句话是说把4个字节的数据给buf[1];
	//来来,来一起分析。memcpy函数表示拷贝,
	//是将从&a开始的sizeof(int)个字节的数据拷贝到buf+1的位置。buf+1就表示buf[1]。
	//那前面定义了,buf[1]只有一个字节(是字符型)。因此,虽然函数拷贝了四个字节,
	//但实际上只有一个字节的数值放进去了,那放进去的是哪一个呢?
	//(我估计会因大小端存放方式不同而不同。不懂请忽略)
	printf("%d\n",buf[1]);  //这句话就是输出buf[1]
	char* p = buf;  //其实数组就是指向字符的指针(这个形式清记住。)
	printf("%d\n", *(int*)(p + 1));  //这句话,首先,p+1表示移动到buf[1]位置,因为步长为1,
	//然后通过(int *)强制类型转换,转换成int *,这时候就可以去从&buf[1],
	//该地址开始的四个字节的数据了。然后通过解引用得出具体数值。OK!
}

指针步长讲完。

发布了27 篇原创文章 · 获赞 0 · 访问量 427

猜你喜欢

转载自blog.csdn.net/weixin_40007143/article/details/103706401