C语言-数组

1. 概念

1.1 分类

根据数据类型的不同:

  • 数值数值
  • 字符数组/字符串
  • 指针数组
  • 结构数组

1.2 概念

C语言数组在存储时,是行优先
多维数组下,如二维数组,可以看成是嵌套:一个数组可以作为外部数组的一个元素
C语言数组是静态的,不能插入或删除元素(数组一旦被定义,占用的内存空间就是不可改变的,但是可以修改和读取数值),如果要改变一个数组,就要再造一个数组

1.3 数组类型

注意:数组也是有类型的!!!

int a[10]; // 数组a的类型是int [10]

1.4 数组与内存

数组内存时连续的,(连续的内存为指针操作和内存处理提供了方便),数组可以作为 缓存 使用,(临时存储数据的一块内存)

1.4.1 数组越界与溢出

数组下标越界不会产生编译错误,只会在运行过程中产生错误

int a[3]={1,2,3}; // printf a[3],如a[4,5,6]...以后不知道是什么值
// 对于int a[3]={1,2,3,4,5};但是高级编译器会报错,或者
// 对于下面这种 编译器也不会报错
int a[3];
for (int i=0;i<5;i++)
{
    a[i]=i;
}
// 这里sizeof(a)==sizeof(int)*3

负数索引

尽量不要使用负数索引,负数索引 指的是arr[0]的内存空间中的前一个元素

越界与内存

数组越界赋值,有可能修改其他内存空间中其他数据的值

int main()
{
	// 下面的变量 可能在内存空间上是连续的
	int var1=10;
	int a[3];
	int var2=30;

	for (int i=-1;i<=4;i++)
	{
		a[i]=i;
	}

	for (int i=-1;i<=4;i++)
	{
		printf("%d\n",a[i]);
	}

	printf("%d\n",var1); // 可能变成了-1
	printf("%d\n",var2); // 可能变成了 4
}

1.5 变长数组

变长数组仍然是静态数据,一旦定义之后都是不可变的
array[length]
scanf("%d",&length);array[length]

1.6 静态分配与动态分配

#define MaxSize 10
typedef struct
{
    int data[MaxSize]; // 静态分配
    int length;
}SqList;

1.6.1 动态分配与malloc

new是C++中的关键字
malloc 返回的是被 动态分配 内存的首地址

malloc 可以用来返回数组指针、结构指针等

#include <iostream>
#include <cstdlib>

#define MAX 3

using namespace std;

int main()
{
    double *ptd=(double *)malloc(MAX*sizeof(double));

    if (ptd==NULL)
    {
        cout<<"ERROR: memory created wrong!";
        exit(EXIT_FAILURE);
    }

    return 0;
}

例子2

#include <stdlib.h>

#define InitSize 10
typedef struct 
{
    int *data; //动态分配
    int MaxSize;
    int length;
}SeqList;

void InitList(SeqList *L)
{
    L->data=(int*)malloc(InitSize*sizeof(int));
    L->length=0;
    L->MaxSize=InitSize;
}

void IncreaseSize(SeqList *L,int len)
{
    printf("%p\n",L->data); //00000000006916A0
    int *p=L->data; // 这是为了防止内存泄漏
    printf("%p\n",p); //00000000006916A0
    L->data=(int*)malloc((L->MaxSize+len)*sizeof(int)); //0000000000691700 ,就把L->data当做一个指针就行了
    printf("%p\n",L->data);
    for (int i=0;i<L->length;i++)
    {
        L->data[i]=p[i];
    }
    L->MaxSize+=len;
    free(p); // 这是为了防止内存泄漏
}

int main()
{
    SeqList L;
    InitList(&L);
    // for (int i=0;i<InitSize;i++)
    // {
    //    printf("%d\t",L.data[i]);
    // }
    // printf("\n%d\n",L.length);
    IncreaseSize(&L,5);
    return 0;
}

1.6.2 malloc 与二维数组 – 还没看!!!

https://blog.csdn.net/fengxinlinux/article/details/51541003

1.6.3 calloc/realloc

  • void * calloc(size_t n,size_t size)

在堆区分配n*size 字节的连续空间
成功分配返回内存地址,失败则返回NULL

n: 单元个数(相当于有多少个单位),size: 单位字节数(每个单元有多少个字节)

  • void * realloc(void *ptr,size_t size)

对ptr指向的内存重新分配size大小的空间,size可大可小
成功分配返回内存地址,失败则返回NULL
realloc 之后id不变

free(p ) 并不改变指针p的值,p依然指向以前的内存,为了防止再次使用该内存,将p=NULL

#include <iostream>
using namespace std;

#define N 5
#define N1 7
#define N2 3

int main()
{
    int *ip;
    int *large_ip;
    int *small_ip;

    if ((ip=(int *)malloc(N*sizeof(int)))==NULL)
    {
        cout<<"malloc ERROR";
        exit(1);
    }

    for (int i=0;i<N;i++)
    {
        ip[i]=i;
        cout<<ip[i]<<endl;
    }

    // cout<<ip<<endl; // 0x621f50
    // (int *)realloc(ip,N1*sizeof(int));
    // cout<<ip<<endl; // 0x621f50
    
    if ((large_ip=(int *)realloc(ip,N1*sizeof(int)))==NULL)
    {
        cout<<"realloc large ERROR";
        exit(1);
    }

    // 两个指向同一块内存
    cout<<ip<<endl; // 0x661f50
    cout<<large_ip<<endl; // 0x661f50

    for (int i=N;i<N1;i++)
    {
        large_ip[i]=i;
    }
    for (int i=0;i<N1;i++)
    {
        cout<<large_ip[i]<<" "<<ip[i]<<endl;
    }

    if ((small_ip=(int *)realloc(large_ip,N2*sizeof(int)))==NULL)
    {
        cout<<"realloc small ERROR";
        exit(1);
    }

    // 三个指向同一块内存
    cout<<ip<<endl; // 0x661f50
    cout<<large_ip<<endl; // 0x661f50
    cout<<small_ip<<endl; // 0x661f50

    // 缩小只会在有效范围内数值正常,越界会产生随机数
    for (int i=0;i<N2;i++) // N 或者N1都是错的
    {
        cout<<small_ip[i]<<" "<<ip[i]<<endl;
    }

    free(small_ip); // 只用释放small_ip 其他两个被系统回收
    small_ip=NULL;
    return 0;
}

1.7 数组与指针 – 见指针

https://blog.csdn.net/L_fengzifei/article/details/126411708

2. 数值数组

创建数组:赋值个数小于开辟的内存空间,且剩余的元素会被初始化为0,
对于整型,初始化为0
对于浮点型,初始化为0.0
对于字符数组,字符数组初始化为\0

int a[10]={1,2,3};
// 只能以循环的方式进行输出
for (int i=0;i<10;i++){
    printf("%d\n",a[i]);
}

// 方法2
int a[]={1,2,3,4}; // 自动计算需要开辟的内存空间

// 比较常用的写法
int a[10]={0};

数组越界不会被编译器检测出来错误
下面还有一个越界的例子 “2.1 数值数组与sizeof”

int a3[3];
for (int i=0;i<3;i++)
{
    a3[i]=i;
}

for (int i=0;i<3;i++)
{
    printf("%d\t",a3[i]); // 0,1,2
}

printf("\n");

// 越界不会报错!!!
// 但返回的是随机值
for (int i=0;i<10;i++) 
{
    printf("%d\t",a3[i]); // 0,1,2,3,3... 随机数
}

结构体之间可以进行赋值操作
数组之间不可以进行赋值操作

struct1=struct2
// arr1=arr2 // 这种不行

2.1 数值数组与sizeof

越界也会按照开辟的内存,计算sizeof()

// 完全不赋值
int a[10];
int* p=a;
printf("%d\n",sizeof(a)); //40
printf("%d\n",sizeof(p)); //8

// 部分赋值
int a2[10]={1,2,3};
int* p2=a2; 
printf("%d\n",sizeof(a2)); //40 , 对应的是开辟的数组
printf("%d\n",sizeof(p2)); //8

// 对于越界赋值,编译器不会出错
// 但是后面访问的时候,并不会按越界赋值的数值显示,显示的依然是随机数
int a3[3];
for (int i=0;i<5;i++)
{
    a3[i]=i;
}
int* p3=a3;
printf("%d\n",sizeof(a3)); // 12
printf("%d\n",sizeof(p3)); // 8

/* == 例子2 == */
// 动态计算
int a4[]={1,2,3};
printf("%d\n",sizeof(a4)); //12,计算的还是对应的初始化开辟的内存
for (int i=0;i<5;i++)
{
    a4[i]=i; // 越界赋值,编译器不报错
}
printf("%d\n",sizeof(a4)); // 12

for (int i=0;i<5;i++)
{
    printf("%d\n",a4[i]); // 越界打印不会报错,打印的依然是随机数
}

2.2 一维数组与指针

数组指针:是指向数组的指针
指针数组:数组中存放的是指针

数组名可以认为是做一个指针,指向数组的第0个元素,但是数组名不可变,数组名本身是常量,所以 数组本身是指针这个说法不准确!!!
第0个元素的地址称为数组的首地址
数组指针 指向的是数组中的一个具体元素,而不是整个数组!!!

指向数组的指针(数组指针),与数组名不同,数组名不可以改变,而数组指针可以改变
注意: ++具有inplace操作
(下面的具有 右结合性)
*p++ 等价于*(P++),不能使用*arr++,因为数组名不能改变
*++p 等价于*(++P) 等价于*(p+1)
(*p)++只对数值进行改变

datatype *p=arr p+1相当于移动了一个datatype类型的字节

int a[3]={1,2,4};
int* p=a;
// 下面三个是等价的
printf("%p\n",p);
printf("%p\n",a);
printf("%p\n",&a[0]);

// printf("",a++); // 这个是错误的,数组名是不能改变的
printf("%d\n",*p++); // 1 ,*p赋值之后,再++ !!!
printf("%d\n",*(p++)); // 1,*p赋值之后,再++ !!!
printf("%d\n",*p+1); // 2 等价于(*p)+1
printf("%d\n",*(p+1)); // 2 
printf("%d\n",*(++p)); //2 
printf("%d\n",*++p); //2 
int main()
{
    int a[3]={1,4,5};

    int *p=a;
 
    // printf("%d\n",*p++);
    // printf("%d\n",*p);
    printf("%d\n",(*p)++);
    printf("%d\n",*p);
    // printf("%d\n",*(p++));
    // printf("%d\n",*p);

    for (int i=0;i<3;i++)
    {
        printf("%d\n",a[i]);
    }
    return 0;
}

重要

[]符号具有取内容的作用
a[i],*(a+i),p[i], *(p+i) 这四个是等价的
&a[i],(a+i),&p[i], (p+i) 这四个是等价的

int a[3]={1,2,3};
for (int i=0;i<3;i++)
{
    printf("%d,%d,%d,%d\n",a[i],*(a+i),p[i],*(p+i));
    printf("%p,%p,%p,%p\n",&a[i],(a+i),&p[i],(p+i));
}

2.2.1 数组名/数组名地址

int main()
{
    int a[3]={1,2,3};
    printf("%p\n",a); // 0x61fe14 数组首元素地址 a的类型为 int
    printf("%p\n",&a); // 0x61fe14 整个数组的起始地址, 类型为int [3]
    printf("%p\n",&a[0]); // 0x61fe14
    printf("%p\n",a+1); // 0x61fe18 = 0x61fe14 + 0x04 首元素下一个元素的地址
    printf("%p\n",&a+1); // 0x61fe20 = 0x61fe14 + 0x0C(12) 跳过了整个数组

	// int *p=&a; // 这种是错误的 类型不匹配

    return 0;
}
char str[10]="hello"
func(str) 等价于 func(&str)

2.3 memset/memcpy

  • memset

将一块内存中的内容全部设置为指定的值
void *memset(void *s,int c,size_t n) s为要填充的内存块的首地址,c为被设置的值(以 补码 进行字节初始化),n为要设置的字节数(内存块长度),返回一个指向该内存的指针

注意:按字节对内存块进行初始化,所以不能用它将数组初始化为0和-1之外的其他值
即,初始化时并不关心要初始化的数组的类型是什么,均以字节为单位进行初始化

#include <string.h> // #include <cstring>
// 例子1
int a[4];
memset(a,1,sizeof(a))
1 == 0x00000001 以字节为单位初始化,则只能截取低80x01
则每个元素初始化成0x01010101 也就是16,843,009

// 例子2
// 下面两个是等价的
memeset(a,-1,sizeof(a)); // -1 在内存中的补码低8位是0x 1111 1111
memeset(a,255,sizeof(a)); // 255 在内存中的补码低8位是 0x 1111 1111

// 例子3
// 清空结构体
typedef struct Stu
{
	char name[20];
	int age;
}Stu;

Stu stu1;
memset(&stu1,0,sizeof(Stu))

特别注意!!!
memset函数是一个运行时函数,其在运行时才能确定其要设置的字节数,因此不能在全局作用域使用
全局作用域下的变量进行初始化时,只能使用常量表达式或编译时常量(即具有静态存储期的变量),(所以memset不能在全局作用域下进行变量初始化)

下面的例子是错误的!!!

#include <iostream>
#include <cstring>

using namespace std;

int arr[10];
memset(arr,0,sizeof(arr));

int main()
{  
    return 0;
}

下面的例子是对的

static int a[10];

void func()
{
	memset(a,0,sizeof(a);
}
  • memcpy

内存复制
void *memcpy(void *destin,void *source,unsigned n)source指向的地址为起点,将连续的n个字节数据,复制到以destin指定的地址为起点的内存中,返回一个指向destin内存的指针(注意返回的区别)
https://blog.csdn.net/GoodLinGL/article/details/114602721

char a[10] = "abcdefgh";
unsigned n = 2;
cout<<&a<<endl; // 0x61fe06
void * p = memcpy(a+3, a, n); // p指向a,a
// memcpy(a+3, a, n); // p指向a,a

cout<<a<<endl; // abcabfgh
cout<<&a<<endl; // 0x61fe06
cout<<p<<endl; // 0x61fe09
cout<<(char *)p<<endl; // abfgh

memove 允许数据重叠
memcpy 不允许数据的内存空间存在重叠

3. 多维数组

创建数组:可以按行创建,也可以连续创建。部分赋值创建的时候,其他赋值元素默认初始化为0(注意部分赋值和完全不赋值随机初始化的区别,完全不赋值随机初始化的时候不是0)
如果是全部赋值,第一维度可以不写
可以理解为嵌套

// 按行创建
int a[2][3]={{1,2,3},{4,5,6}};

// 按连续内存创建
int a[2][3]={1,2,3,4,5,6};

// 部分赋值
int a[2][3]={{1},{2},{3}};
int a[2][3]={{0,1},{0,1,2},{3}}

// 第1维度自动推导
int a[][3]={1,2,3}

int a[][3]={1,2,3};
for (int i=0;i<1;i++){
    for (int j=0;j<3;j++){
        printf("%d\n",a[i][j]); // 可以打印但是不能赋值
    }
}

// 注意!!!
// 下面的初始化是错误的,无法实现自动推导
// int a[][3];
// for (int i=0;i<3;i++)
// {
//     for (int j=0;j<3;j++)
//     {
//         a[i][j]=i*j;
//     }
// }

// 嵌套理解
a[3][4] 中 a[0]是一个数组(也是一个一维数组名)

3.1 二维数组与一维数组的关系

a[2][3] 可以看成是2个一维数组,每个一维数组的数组名是a[0],a[1]
int arr[m] [n] arr数组有m个元素,每个元素的类型是一维数组int [n]

3.2 多维数组与sizeof

int a[2][3];
for (int i=0;i<2;i++)
{
    for (int j=0;j<3;j++)
    {
        a[i][j]=i*j;
    }
}

int (*p)[3]=a; 
printf("%d\n",sizeof(a)); // 24
printf("%d\n",sizeof(a[0])); // 12(因为相当于一个一维数组名)
printf("%d\n",sizeof(p)); // 8
printf("%d\n",sizeof(p[0])); // 12

3.3 多维数组与指针

https://blog.csdn.net/L_fengzifei/article/details/126411708

在这里插入图片描述

a[2][3] 可以看成是2个一维数组,每个一维数组的数组名是a[0],a[1]

对于一维数组及其指针 type *p=arr p+1相当于移动了一个type类型的字节
对于二维数组及其指针,p+1应该移动整个一维数组对应的字节,type (*p)[n],(*p) 表示指针,int [n] 表示指针指向的数据类型 也就是每行的数据类型

int a[1][3]={1,2,3};
for (int i=0;i<1;i++){
    // for (int j=0;j<3;j++){
    //     printf("%d\n",a[i][j]);
    // }
    printf("%p\n",a[i]); // &a[i][0] 与a[i] 等价
    printf("%p\n",&a[i][0]); // &a[i][0] 与a[i] 等价
    printf("%d\n",a[i][0]); // 这是数值
}

int a[2][3]={{1,2,3},{4,5,6}};
// 下面三个是等价的 
// *a[2][3] 可以看成是2个一维数组,每个一维数组的数组名是a[0],a[1]*
printf("%p\n",*a);
printf("%p\n",a[0]);
printf("%p\n",&a[0][0]);


// 下面是等价的
int (*p)[3]=a;
printf("%p\n",*(p+1));
printf("%p\n",*(a+1));
printf("%p\n",a[1]);
printf("%p\n",&a[1][0]);

重要

a=&a[0]
a+i=&a[i]
a[i]=&a[i][0]

*a=a[0]
*(a+i)=a[i]
*a[i]==a[i][0]

重要

a[i]==*(a+i)==p[i]==*(p+i)
a[i][j]==*(a[i]+j)==*(*(a+i)+j)==p[i][j]==*(p[i]+j)==*(*(p+i)+j)

int (*p)[3]=a;
for (int i=0;i<2;i++)
{
    for (int j=0;j<3;j++)
    {
        printf("%d,%d,%d,%d,%d,%d\n",a[i][j],*(a[i]+j),*(*(a+i)+j),p[i][j],*(p[i]+j),*(*(p+i)+j));
    } 
}

/*  /// 补充 ///
    // 理解
    对于1维数组
    int a={1,2,3};
    int* p=a;
    a[1]==*(P+1)==*(a+1)==p[1]
    &a[1]==p+1==a+1

    // 二维数组
    // 看成嵌套的一维数组
    int a[][2]={1,2,3,4};
    int (*p)[2]=a; 
    *(*(p+1)+1) ==a[1][1]
    p指向了一个都是指针的数组(首地址),而数组元素的每个指针,指向了一个一维数组(首地址)
    p+1==a[1]==&a[1][0]
    *(p+1)==a[1]==&a[1][0] // 这个记忆方法放到下面可以理解

    // 下面三个方法是等价的
    printf("%d\n",*(a[1]+1)); //a[1]也是一个指针,表示一维数组
    printf("%d\n",*(*(p+1)+1));  // *(p+1) p+1表示一个一维数组,
    printf("%d\n",a[1][1]);
*/ 

重要 - 第二种表达方法

int a[2][3]={{1,2,3},{4,5,6}};

int *p=&a[0][0]; // p=a p=&a[0] *p=a[0] a=&a[0] a[0]=&a[0][0]
for (int i=0;i<2;i++)
{
    for (int j=0;j<3;j++)
    {
        printf("%d,%d\n",a[i][j],*((p+i*3)+j));
    }
}

在这里插入图片描述

4. 字符型数组

4.1 字符数组

对于字符型数组,赋值个数小于开辟的内存空间,则初始化为"\0"printf("%c","\0")打印不出来任何东西
字符型数组与数值数组的基本性质是一样的

字符数组就是字符串
c语言中没有字符串类型,所以借用字符数组存放一个字符串

char str[10]={0} // 默认初始化为\0

// 一维
char s[10];

// 多维
char s[2][3];

// 部分赋值
char c[20]={' ','2'};

// 全元素赋值,自动推导长度,这个属于字符数组,不属于字符串
char d[]={'h','e',...};

// 注意!!!
char s[10];
// s="hello world"; // 这种方式不行
s[0]='0'; // 只能是下面这种方式
s[1]='2';

4.2 C语言字符串

4.2.1 字符串创建/访问

字符串 其实是字符数组
字符数组就是字符串
c语言中没有字符串类型,所以用 字符数组存放一个字符串
可以通过索引获得元素字符
在创建字符串的时候不能超出开辟的范围,不然会出错

注意:下面创建的字符串,具有读写权限
注意:如果复制直接"string"复制,而是逐个字符赋值给字符数组,要想创建字符串就要手动加上\0

char s[30]={"hello world"}; // 等价于char s[30]={'h','e',...};

// 其他常用方式
char s[10]="hello world";

char s[]={"hello world"};
char s[]="hello world";

// 为了避免\0的出现,最好的解决办法是
char s[]={0}; // 进行默认初始化

// 索引
char a[10]="hello";
printf("%c\n",a[1]);

长度

strlen计算不包括结尾的\0
sizeof计算包含结尾的\0

#include <string.h> // 必须要进行包含
char a[]="hello";
printf("%d\n",sizeof(a)); // 6
printf("%d\n",strlen(a)); // 5

// sizeof 要么等价于实际开辟的内存空间,要么要包含`\0`
char a[10]="hello";
printf("%d\n",sizeof(a)); // 10 , 注意这是要开辟的数组!!!
printf("%d\n",strlen(a)); // 5


// 对于数组越界 !!!
char a[3]="hello";
printf("%s\n",a); // 会发生未知字符
printf("%d\n",sizeof(a)); // 3
printf("%d\n",strlen(a)); //6 随机值,无法准确判断

char a[6]="hello worlld";
printf("%d\n",strlen(a)); // 9 是随机值,无法准确判断

第二种创建方式

char s1[]="string1";
char *s2="string2"

// 初始化注意事项
// char s1[10];
// s1="hello"; // 这种不行
char *s2;
s2="world"; // 这种可以

char s1[]="string"

这种方法定义的字符串所在的内存既有读取权限又有写入权限(可变),可以用于输入与输出函数
[]可以指明字符串的长度,如果不指明则根据字符串自动推算
声明字符串的时候,如果没有初始化,(由于无法自动推算长度,只能手动指定)char s[10]

char *s1="string"

这种方法定义的字符串所在的内存只有读取权限,没有写入权限(不可变),只能用于输出函数

对于 字符串修改
https://blog.csdn.net/qq_31347869/article/details/105877116!!!
也就是数组形式的字符串可以进行字符串修改(具有读写权限)
指针形式的字符串不可以进行修改(只有读权限)

注意:
数组型字符串不可以进行互相赋值
指针型字符串可以进行相互赋值

// 下面是错的
// char a1[10];
// char a2[10]="hello"; // 这一个表达式是对的
// char a3[10]=a2;

// 下面是可以的
char *a1="";
char *a2="hello";
printf("%s\n",a1);
a1=a2;
printf("%s\n",a1);

字符串访问

char arr1[]="hello world";
char *arr2="hello cpp";

for (int i=0;i<11;i++)
{
    printf("%c,%c",arr1[i],*(arr1+i));
}
printf("\n");
for (int i=0;i<9;i++)
{
    printf("%c,%c\n",arr2[i],*(arr2+i));
}

printf("%s\n",arr1); // hello world
printf("%s\n",arr2); // hello cpp
printf("%s\n",arr1+1); // ello world
printf("%s\n",arr2+2); // llo cpp

4.2.2 字符串细节

4.2.2.1 \0

\0不能显示,也没有控制功能
字符串以\0结尾,注意字符数组不以\0结尾
字符串逐个扫描,一旦遇到\0结束处理

"string"会在字符串自动在末尾添加\0
字符串的字符个数,要包括后面结尾的\0
创建的时候,要为\0,多开辟一个内存空间,所以总数要比字符串+1

// 创建字符串数组,下面这种方式只能逐个元素创建
// 当创建的字符个数小于开辟的数量,剩下的为随机初始化的内容,有可能并不是\0,注意随机初始化和部分初始化的
// 所以输出的可能字符个数可能大于30,因为printf直到\0的时候才会结束输出
char s[30];
char c;
int i;
for (c=65,i=0;c<=90;c++,i++){
	s[i]=c;
}
printf("%s\n",s)

// 手动添加\0
// \0 的ASCII编码值是0
// 下面两个等价
s[i]=0; 
s[i]='\0';

4.2.3 字符串/地址/指针

字符串与地址

char a='c';
printf("%p\n",&a);
printf("%p\n",a); // 这两个地址不同,第二个写法应该取不出地址

char b[10]="hello";
printf("%p\n",&b); 
printf("%p\n",b); // 字符串的名字是地址
printf("%p\n",&b[0]); // 也是字符串第一个元素的地址

int c[10]={1,2,3};
printf("%p\n",&c);
printf("%p\n",c); // 数组名也是地址
printf("%p\n",&c[0]); // 也是数组第一个元素的地址

字符串与指针
https://blog.csdn.net/L_fengzifei/article/details/126411708

char* func()
{
	return "string"
}

对于字符串,整个引号中的内容作为指向该字符串存储位置的指针 !!!

// are字符串的第一个字符的地址
// 字符串可以当做指针*"family"相当于取指针指向的元素,取地址的元素,也就是该字符串的首元素
printf("%s,%#p,%c\n","we","are",*"family"); // we,0x409001,f

遍历的一种写法

void func(char *str)
{
    while(*str) // '\0' 的ASCII 是0
    {
        *str=toUpper(*str); // 自己赋值给自己
        str++;
    }
}

字符串数组形式与指针形式的区别

数组初始化是从静态存储区把一个字符串复制给数组
指针初始化只是复制字符串的地址
《c语言-函数 返回值与局部变量一节的例子》

char arr1[]="hello"; // 栈区
char *arr2="world"; // 常量区

*char 字符串及其地址

char *ptr=new char[10];
printf("%#p\n",&ptr[0]); // 0x7d14d0
printf("%#p\n",ptr); // 0x7d14d0
printf("%#p\n",&ptr); // 0x61fe18
printf("%#p\n",(char *)ptr); // 0x7d14d0
printf("%#p\n",(void *)ptr); // 0x7d14d0

4.2.4 字符串相关函数

strcat/strcpy/strcmp
// strcat 字符串拼接 // inplace操作
char a1[20]="hello";
char a2[10]="world";
printf("%d\n",sizeof(a1)); // 20
printf("%d\n",sizeof(a2)); //10 
strcat(a1,a2);
printf("%s\n",a1);
printf("%d\n",sizeof(a1)); // 20 

char a1[]="hello";
char a2[]="world";
printf("%d\n",sizeof(a1)); // 6
printf("%d\n",sizeof(a2)); // 6
strcat(a1,a2);
printf("%s\n",a1);
printf("%d\n",sizeof(a1)); // 6 ???

// 没有写入权限,失败
// char *a1="hello";
// char *a2="world";
// printf("%d\n",sizeof(a1)); // 20
// printf("%d\n",sizeof(a2)); //10 
// strcat(a1,a2); // 由于没有写入权限所以不行
// printf("%s\n",a1);
// printf("%d\n",sizeof(a1)); // 20 

// strcpy 字符串复制 // inplace操作
// 会将原来的数据全部进行覆盖
// 值得注意的是: strcpy会把\0自动添加进去
char a1[]="hellohello";
char a2[]="world";
printf("%d\n",sizeof(a1)); // 11
printf("%d\n",sizeof(a2)); // 6
strcpy(a1,a2); //world
printf("%s\n",a1);
printf("%d\n",sizeof(a1)); // 11 ???

char a1[20]="hellohello";
char a2[10]="world";
printf("%d\n",sizeof(a1)); // 20
printf("%d\n",sizeof(a2)); // 10
strcpy(a1,a2); //world
printf("%s\n",a1);
printf("%d\n",sizeof(a1)); // 20

问题

// 对于数组越界 !!!
char a[3]="hello";
printf("%s\n",a); // 会发生未知字符
printf("%d\n",sizeof(a)); // 3
printf("%d\n",strlen(a)); //6

char name[5];
strcpy(name,"zhangsan"); 
printf("%s\n",name); // 不发生越界??? 有的编译器会报错warning
数值型/字符串转换

把数字存储为字符串就是存储数字字符
123 -> ‘1’ ‘2’ ‘3’ ‘\0’

字符串转数值型
  • atoi

字符串转整型

// 下面两种字符串都可以
char s[]="123";
char *s="123";
int var=atoi(s);
printf("%d\n",var);
  • atof

字符串转整型

// 下面两种字符串都可以
// char s[]="123";
char *s="123.456";
double var=atof(s);
printf("%f\n",var);
数值型转字符串
  • itoa()
#include<stdlib.h>
int a=10;
char s[10]={0};
itoa(a,s);
printf("%s\n",s)

char a='a';
char buffer[10];
itoa(a,buffer,2); // 2表示转换基数
  • sprintf
// 整型转字符串
int a=120;
char str[20];
memset(str,0,sizeof(str));
sprintf(str,"%d",a);
printf("%s\n",str);
printf("%d\n",sizeof(str));

4.3 字符串数组

https://blog.csdn.net/cnds123321/article/details/122973636
https://blog.csdn.net/u014157109/article/details/118718978

第一种创建方式

char strs[row][col] // row 表示有几个字符串,col每个字符串的最大长度
char strs[2][10]={"hello","world"};

// 下面这种是错误的
// char strs[2][10];
// strs={"hello","world"};

// 上面相当于,因为对于一维来说这也是错误的,a是常量
// char a[10];
// a="hello";

第二种创建方式

char* strs[row] // row 表示有几个字符串,每个字符串的类型是char* ;不能写成char (*strs)[row] 这相当于二维字符数组,每个字符数组有row个字符!!!

char* strs[10]={"hello","world"};
strs[0] 指向第一个字符串的首字符的地址,*strs[0]=='h'
strs[1] 指向第二个字符串的首字符的地址,*strs[1]=='w'

// 下面这种是错误的
// char* strs[10];
// strs={"hello","world"};

指针数组 – 还得再看

数组中所有元素保存的都是指针,就叫做指针数组
dataType *arr[]dataTye* (arr[])
表明是一个数组,每个元素的类型都是dataType*
注意 指针数组和二级数组的区分

int* arr[3]={&a,&b,&c};
int** p=arr; // 定义一个指向指针数组的指针,相当于(int*)* p=arr


char* str[2]={"hello","world"};
printf("%s\n",str[0]); // 输出"hello",char* s="hello";

//下面的方法更好理解
char* s1="hello";
char* s2="world";
char* s[2]={s1,s2};

/*
char *s="hello";

char *s;
s="hello";
*/

6. 结构体数组

数组中每个元素都是结构体

struct stu{
	char* name;
	int num;
}class[5];

// 声明的时候初始化
struct stu{
	char* name;
	int num;
}class[5]={
	{"li",1},
	{"w",2}
};

// 声明的时候初始化,自动推断元素个数
struct stu{
	char* name;
	int num;
}class[]={
	{"li",1},
	{"w",2}
};

// 可以在结构体外,创建结构体数组
struct stu class[]={
    {"li",2},
    {"wang",3}
};

// 访问数据
class[0].name;
class[1].num=2; //修改

6.1 结构体数组指针

因为是数组,所以数组名可以作为元素的首地址

struct stu class[]={
    {"li",2},
    {"wang",3}
};

// 下面两个是等价的,都表示地址
printf("%p\n",class);
printf("%p\n",&class[0]);

// 所以可以利用指针指向地址
struct stu* pstu=class; 

// 下面这两种访问方法都是等价的
struct stu* pstu=class; // 看成普通数组就行
for (int i=0;i<2;i++){
    printf("%s,%d\n",(pstu+i)->num,(pstu+i)->age);
}

for (int i=0;i<2;i++,pstu++){
    printf("%s,%d\n",pstu->num,pstu->age);
}

6.2 结构体数组/指针 进阶

结构体名 不是该结构体的起始地址,更不是该结构体首元素的地址
结构体的起始地址,在值上 等于 该结构体首元素的地址

#include <iostream>

#define LEN 10

using namespace std;

struct names
{
    char first[LEN];
    char last[LEN];
};

struct people
{
    struct names handle;
    char favfool[LEN];
    char job[LEN];
    float income;
};


int main()
{
    // 结构体名 不是该结构体的起始地址
    // 结构体的起始地址,在值上 等于 该结构体首元素的地址
    struct names name1={"li","wang"};
    // std::cout<<name1<<endl;
    printf("%p\n",name1); // 0x61fd40 不等于首元素的地址
    printf("%p\n",&name1); // 0x61fdf0 相当于整个结构体的起始地址 而结构体的起始地址 的值 等于第一个成员的地址
    printf("%p\n",name1.first); // 0x61fdf0 
    printf("%p\n",&name1.first); // 0x61fdf0 该成员(整个成员)的起始地址
    printf("%p\n",&name1.first[0]); // 0x61fdf0 等价于 name1.first

    struct people people1={
        {"li","wang"},
        "apple",
        "ex",
        20.1
    };

    printf("%s\n",people1.handle.first); 
    printf("%s\n",people1.handle.last); 
    printf("%p\n",people1); // 0x61fd10 !!! 说明结构体名不等于首元素的地址也不等于结构体的起始地址
    printf("%p\n",&people1); // 0x61fdc0 整个外层结构体的起始地址
    printf("%p\n",people1.handle); // 0x61fd10 !!! 说明结构体名不等于首元素的地址也不等于结构体的起始地址
    printf("%p\n",&people1.handle); // 0x61fdc0 整个内存结构体的起始地址 等于 整个外层结构体的起始地址
    printf("%p\n",people1.handle.first); // 0x61fdc0 下面三个是数组的地址
    printf("%p\n",&people1.handle.first); // 0x61fdc0 
    printf("%p\n",&people1.handle.first[0]); // 0x61fdc0 
  
    struct people peoples[2]={
        {
            {"li","wang"},
            "apple",
            "ex",
            20.1
        },
        {
            {"zhang","san"},
            "pea",
            "ex2",
            22.1
        },
    };

    // 结构体指针
    struct people *ptr1;
    ptr1=&people1; // 结构体的名字并不等于结构体元素的首地址!!! 指向起始地址是对的,因为类型是匹配的
    cout<<ptr1->favfool<<","<<ptr1->job<<","<<ptr1->income<<endl;

    // 结构体数组指针 把他想象成普通数组的指针就行了
    struct people * ptr;
    // ac - 84 = int 44 一共44个字节
    cout<<&peoples[0]<<" "<<&peoples[1]<<endl; // 0x61fd80 0x61fdac 这个相等于起始地址
    cout<<&peoples<<endl; // 0x61fd80 整个数组的地址
    cout<<peoples<<endl; // 数组首元素的地址,也就是第一个结构体的地址
    // 结构体的名字并不等于结构体元素的首地址!!! 
    
    // version1
    ptr=peoples;
    cout<<ptr<<" "<<ptr+1<<endl; // 0x61fd80 0x61fdac
    cout<<ptr->job<<endl;
    
    // version2
    ptr=&peoples[0]; // struct people 所以+1 跨过了整个数组
    cout<<ptr<<" "<<ptr+1<<endl; // 0x61fd80 0x61fdac ptr+1跨过了整个结构体数组中的一个元素
    cout<<ptr->income<<endl;


    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值