C 预处理指令,指针,类型限定符和数组

目录

一、预处理指令

1、#include 包含头文件

2、#define 定义宏和内联函数

3、#if....#elif....#endif

4、#error

5、#line 

6、#pragma

二、指针

1、指针的定义

2、指针的指针

3、指针的操作

4、指针赋值的类型检查

5、空指针

6、void指针

7、结构指针

8、函数指针

三、类型限定符

1、const 关键字

2、volatile关键字

3、restrict关键字

4、_Thread_local关键字

5、_Atomic关键字

四、数组

1、数组初始化

2、数组边界

3、数组名与指针

4、函数形参中的数组

5、变长数组

6、匿名数组(复合字面量)


一、预处理指令

     预处理指令是以#号开头的代码行,表示在源代码编译前执行的操作,由预处理器负责解释执行。除了执行预处理指令和替换宏定义外,预处理过程还会删除程序中的注释和多余的空白字符。常用预处理指令如下:

1、#include 包含头文件

//表示包含来自标准库或者第三方库的头文件 
#include <stdio.h> 
//表示包含来自当前应用程序的头文件,相对于当前文件夹
#include "hotel.h" 
#include "../Ch08/hotel2.h"

2、#define 定义宏和内联函数

注意宏只是做文本替换,与之相对的是#undef,取消已定义的宏,通常与条件编译配合使用

#include <stdio.h>
#include "../Ch08/hotel2.h"
//定义常量,但是推荐使用const关键字
#define NUM 123
#define NAME "SHL"
//const关键字表示只读变量
const float PI=3.131415;

//定义表达式,注意替换后可能产生意外的效果
# define SQR(X) X*X
#define MAX(x, y) ((x) > (y) ? (x) : (y))

//定义多行函数
#define DOIT(m,n) for(int i=0;i<n;++i)\
    {\
    /*m=m+i; 报错赋值运算的左操作数必须是左值,即编译到此行时无法识别m是否可以赋值,另外,因为宏只是做文本替换,无法实现值的递增 */\
    printTest(m);\
    }

//替换成T_x
#define A(x) T_##x
//替换成"x"
#define C(x) #x
#define T_1 10

//可变宏,...代替传入的多个参数,__VA_ARGS__与...对应
#define PR2(X, ...) printf("Message"#X":"__VA_ARGS__)


void printTest(int x);

int main(void){
	//预处理时将所有的宏做文本替换
	printf("NUM2 test:%d\n",NUM);
	printf("NAME test:%s\n",NAME);

	//替换成3+1*3+1
	int result=SQR(3+1);
	printf("SQR test:%d\n",result);

	int x = 2;
	int y = 5;
	//替换成(x++) > (y++) ? (x++) : (y++)
	int ret = MAX(x++, y++);

	printf("x = %d\ty = %d\tret = %d\n", x, y, ret);

	printf("test-->%d\n",A(1));
	printf("test3-->%s\n",C(1));

	double msg = 10;
	//替换成printf("Message""1"":""msg = %.2f\n", msg);
	PR2(1, "msg = %.2f\n", msg);

	DOIT(10,2);

	return 0;
}

void printTest(int x){
	printf("test-->%d",x);
}

 使用宏相比函数会更快,因为避免了程序控制从被调函数到主函数之间来回跳转的开销,但是用宏定义函数容易产生意想不到的错误,C99提供了替代方法,内联函数,通过inline关键字修饰的内部链接的函数就是内联函数,编译器会将对该函数的调用调换成函数定义的代码,效果和使用宏一致,但是更安全。

#include <stdio.h>

#define ADD(a,b) 2*(a)+(b)
//容易出问题的写法
#define ADD2(a,b) 2*a+b

//声明内联函数
inline static int add(int a,int b);

int main(void)
{
	printf("inline result:%d \n",add(3,4));
	printf("define result:%d \n",ADD(3,4));

	printf("inline result:%d \n",add(1+2,2+2));
	printf("define result:%d \n",ADD2(1+2,2+2));

    return 0;
}

inline static int add(int a,int b){
	return 2*a+b;
}

3、#if....#elif....#endif

    条件编译,其中#if defined 等价与#ifdef,表示如果定义了某个宏,与之相对的是#ifndef,表示未定义某个宏

#include <stdio.h>


#define DEBUG


//没有显示定义就表示未定义该宏
//#undef INFO

int main(){
//两种写法是等价的
//#if defined DEBUG
#ifdef DEBUG
	printf("Debug Test\n");
#elif defined INFO
	printf("Info Test\n");
#else
	printf("Error Test\n");
#endif


//#if undefined INFO 错误的写法
#ifndef  INFO
	printf("Undef Test\n");
#endif

//预处理命令是从按照命令的出现顺序上往下依次处理的,不存在命令本身的先后顺序
#define INFO
#if defined(DEBUG) && defined(INFO)
	printf("defined Test\n");
#endif
}

4、#error

     打印错误信息并停止编译

#include <stdio.h>


#define DEBUG


int main(){
#ifdef DEBUG
//编译时直接报错,停止编译
#error "Debug Test\n";
#endif


#ifndef  INFO
	printf("Undef Test\n");
#endif

}

5、#line 

     改变编译器默认指定的文件名与行号信息,即默认宏__LINE__和__FILE__的定义,通常用于让 C 编译器的错误消息指向源文件中相应的行。

#include <stdio.h>

//line命令指定下一行代码的行号是1200,指定当前源代码的文件名为primary.c, 后面的primary.c是可选的
#line 1200 "primary.c"


int main(){
	printf( "This message was printed by line %d in the file %s.\n",__LINE__, __FILE__ );
}

6、#pragma

     向编译器提供额外信息的标准方法,具体使用依赖于编译器提供的选项和实现,比如#pragma message在Visual C下可用,在GNU C下就会报错,尽量不要使用。

#include <stdio.h>


#define DEBUG


int main(){
#ifdef DEBUG
#pragma message "test msg\n";
#endif

}

      参考:详解宏定义(#define)

二、指针

1、指针的定义

    指针本质就是一个值为内存地址的变量,通过&获取一个变量的内存地址,通过*可以获取一个指针指向的变量的值。内存地址通常是一个无符号整数,但是指针并不是整数类型,而是一个独立的新数据类型,使用printf打印时有专门的格式转换符%p。

#include <stdio.h>
void interchange(int u, int v);
void interchange2(int *u, int *v);

int main(void)
{
    int x = 5, y = 10;
    
    //*a表示声明a是指针变量,*a指向的变量是一个int类型变量
    int *a=&x;
    printf("interTest x=%d,a=%p.\n",*a,a);

    printf("Originally x = %d and y = %d.\n", x , y);
    //C语言中是值传递而非引用传递,所以x,y的值不会改变
    interchange(x, y);
    printf("Now x = %d and y = %d.\n", x, y);
    
    printf("Originally2 x = %d and y = %d.\n", x , y);
    //把x,y两个变量的指针复制一份传递到方法中,本质还是值传递,不过方法通过指针操作变量等于操作原来的变量,
    //所以x,y的值会变
    interchange2(&x, &y);
    printf("Now2 x = %d and y = %d.\n", x, y);

    return 0;
}

void interchange(int u, int v)
{
    int temp = u;
    u = v;
    v = temp;
}

void interchange2(int *u, int *v)
{
    int temp = *u;
    *u = *v;
    *v = temp;
}

2、指针的指针

     指针本身也是一个内存变量,也有内存地址,指针的指针就指向该内存地址

#include <stdio.h>

int main() {
	//c是a的指针的指针,c和b指向的内存地址不同
	int a = 1;
	int *b = &a;
	int **c = &b;
	printf("&a=%p\n", &a);
	printf("b=%p\n", b);
	printf("c=%p\n", c);
	printf("*c=%p\n", *c);
	printf("**c=%d\n", **c);
	return 0;
}

   如果希望修改一个指针变量本身所指向的数据,在方法传递的过程中必须使用指针的指针,否则因为值传递的关系,指针变量指向的数据不会改变,如下示例:

#include <stdio.h>

void set(char ** a){
   *a="test";
}

void prt(void ** s){
	printf("print s->%s\n",*s);
}

void set2(char * a){
   a="test2";
}

void prt2(void * s){
	printf("print2 s->%s\n",s);
}

int main(){
	char * a;
//因为a是一个指针,所以为了改变a的指针指向的数据所以这里传入a的地址,即指针的指针,
	//如果直接传入a因为值传递的关系,变量a本身不会改变
	set(&a);
	prt((void **)&a);
	set2(a);
	prt2((void *)a);
	return 0;
}

3、指针的操作

     指针除正常的赋值,解引用,取址外,还可跟整数加减,两个指针相减,同类型的两个指针进行比较。因为指针的值是内存地址,其在形式上是一个表示字节数的无符号整数,所以指针跟整数N加减就是在内存地址上加减N个存储单元长度,存储单元长度是指针指向的变量的字节数,两个指针相减的结果是两个无符号整数相减除以存储单元长度的值。注意不能对未初始化的指针做解引用,此时指针对应的内存空间的值不确定,执行结果不确定。

#include <stdio.h>
int main(void) {
	int urn[5] = { 100, 200, 300, 400, 500 };
	int * ptr1, *ptr2;

	//执行报错Segmentation fault,此时ptr1尚未初始化,对应的内存空间存储的地址不是本进程地址空间
	//内的地址,解引用访问了不存在的内存
//	int a=*ptr1;
//	printf("test a=%d\n",a);

	//ptr1指向数组urn第一个元素,等价于ptr1=&urn[0]
	ptr1 = urn;
	//ptr2指向数组urn第三个元素
	ptr2 = &urn[2];


	//获取本地int类型变量的字节数
	printf("int size=%d\n",sizeof(int));
	//加减法操作,ptr1指向的变量为int类型
	int *ptr3=ptr1 + 3;
	printf("ptr1=%p, ptr1 + 3 = %p, *(ptr1 + 3) = %d\n",ptr1, ptr3, *ptr3);
	int *temp=ptr3;
	ptr3=ptr3-2;
	printf("ptr3=%p, ptr3 - 2 = %p, *(ptr3 - 2) = %d\n",temp, ptr3, *ptr3);
	ptr1++;
	printf("ptr1++, ptr1 = %p, *ptr1 =%d\n", ptr1, *ptr1);
	ptr1--;
	printf("ptr1--, ptr1 = %p, *ptr1 =%d\n", ptr1, *ptr1);

    //指针相减,相减的结果是指针指向目标变量的长度的整数倍
	printf("ptr1 = %p, ptr2 = %p,ptr2 - ptr1 = %td\n", ptr1, ptr2,ptr2-ptr1);
    //指针比较
	printf("ptr1>ptr2 result-->%d\n", ptr1>ptr2);

	double d=1.2;
	double *ptr4=&d;
	//编译报错,不同类型的不同操作
//	printf("ptr1 = %p, ptr4 = %p\n,ptr4 - ptr1 = %td\n", ptr1, ptr4,ptr4-ptr1);
	//不同类型比较,编译时警告未做类型转换
	printf("ptr1 = %p, ptr4 = %p, ptr1>ptr4 result-->%d\n", ptr1, ptr4,ptr1>ptr4);


	return 0;
}

4、指针赋值的类型检查

     普通数据类型变量可以在损失精度的情况下做隐式类型转换,而指针则不行,指向不同类型变量的指针不能做赋值操作

#include <stdio.h>

int main() {
	int n=5;
	//隐式类型转换
	double d=n;
	int * p1=&n;
	double *p2=&d;
	//指向不同类型变量的指针不能赋值,GNU C下编译会警告,从不兼容的指针类型赋值
	p1=p2;
	//结果是p1=0,表示不能这样赋值
	printf("p1=%d \n",*p1);

}

5、空指针

      空指针表示不指向任何有效数据的指针,返回指针的函数可返回空指针表示执行失败或者异常

#include <stdio.h>
#define STLEN 10
int main(void)
{
    char words[STLEN];
    
    puts("Enter strings (empty line to quit):");
    //fgets函数返回读取的字符串的指针,如果读取失败返回NULL空指针
    while (fgets(words, STLEN, stdin) != NULL && words[0] != '\n')
        fputs(words, stdout);
    puts("Done.");
    
    return 0;
}

6、void指针

      void指针表示通用指针,即不指向任何具体数据类型的指针,如malloc()函数返回的指针,使用时需要将其强转至目标类型的指针

#include <stdio.h>
#include <stdlib.h> /* for malloc(), free() */

int main(void)
{
    int * ptd;
    
    printf("int size:%d\n",sizeof (int));

    //malloc函数返回的指针是void,需要将其强转成指向具体类型的指针,参数是分配内存的字节数
    //这里分配了一个有4个double元素的数组
    //注意不能修改ptd指向的内存地址,否则free函数释放时会有问题
    ptd = (int *) malloc(4 * sizeof (int));
    //如果没有可用内存,分配失败则返回NULL空指针
    if (ptd == NULL)
    {
        puts("Memory allocation failed. Goodbye.");
        //exit函数标识异常退出,可以指定退出码
        exit(EXIT_FAILURE);
    }

    ptd[0]=1;
    ptd[2]=1;
    printf("ptd[0]=%d,ptd[2]=%d\n",ptd[0],ptd[2]);

    //必须通过free函数释放申请的内存,否则有内存泄漏问题
    free(ptd);
    
    return 0;
}

7、结构指针

     结构指针是指向结构体的指针,需要注意通过指针获取字段属性的方式跟通过结构变量获取不一样

#include <stdio.h>
#define MAXTITL  41
#define MAXAUTL  31

struct book {
    char title[MAXTITL];
    char author[MAXAUTL];
    float value;
};


int main(void)
{
    //声明指向结构体book的指针
    struct book * pt;
    
    struct book b3={
    		"ab",
    		"ac",
			//具有自动存储期的变量,可以使用其他变量初始化
    		a
    };
    //结构变量通过.访问结构成员
    printf("b3-->%s,%s,%f\n",b3.title,b3.author,b3.value);

    //初始化结构指针,结构指针通过->访问字段成员
    pt=&b3;
    printf("pt-->%s,%s,%f\n",pt->title,pt->author,pt->value);

    return 0;
}

8、函数指针

    函数本质是一组汇编指令,在内存中也有地址,函数指针指向函数在内存中的地址,通过函数指针可以把函数本身作为参数传递给其他参数。

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define LEN 81

void show(void (* fp)(char *), char * str);
void ToUpper(char *);
void ToLower(char *);

int main(void)
{
	char test[]="test";
	//声明一个函数指针fp,要求返回类型和参数类型都必须匹配
    //将函数声明中的函数名用(*fp)替换即表示一个函数指针
	void (* fp)(char *)=ToUpper;
	//C标准中两种方式等价
//    (*fp)(test);
    fp(test);
    puts(test);
	fp=ToLower;
	(*fp)(test);
	puts(test);

	//将函数指针作为参数传递给函数
	show(ToUpper,test);
	show(fp,test);



    return 0;
}

void ToUpper(char * str)
{
    while (*str)
    {
        *str = toupper(*str);
        str++;
    }
}

void ToLower(char * str)
{
    while (*str)
    {
        *str = tolower(*str);
        str++;
    }
}


void show(void (* fp)(char *), char * str)
{
    (*fp)(str);
    puts(str);
}

     可以使用typedef给上述函数指针设置一个别名,方便书写,如下示例:

#include <stdio.h>
#include <string.h>
#include <ctype.h>

typedef void (* fp)(char *);

void show(fp func, char * str);
void ToUpper(char *);
void ToLower(char *);

int main(void)
{
	char test[]="test";
	//声明一个函数指针fp,要求返回类型和参数类型都必须匹配
    //将函数声明中的函数名用(*fp)替换即表示一个函数指针
	fp func=ToUpper;
	//C标准中两种方式等价
//    (*fp)(test);
    func(test);
    puts(test);
    func=ToLower;
	(*func)(test);
	puts(test);

	//将函数指针作为参数传递给函数
	show(ToUpper,test);
	show(func,test);



    return 0;
}

void ToUpper(char * str)
{
    while (*str)
    {
        *str = toupper(*str);
        str++;
    }
}

void ToLower(char * str)
{
    while (*str)
    {
        *str = tolower(*str);
        str++;
    }
}


void show(fp func, char * str)
{
    (*func)(str);
    puts(str);
}

三、类型限定符

1、const 关键字

      const修饰变量表示该变量不可修改,可以修饰普通数据类型变量,数组变量,指针变量,但是这种检查是编译时完成的而不是运行时判断的;另外,const指针变量不能赋值给普通指针变量,为了避免这种情形下可以通过普通指针变量修改const指针变量指向的变量值。

#include <stdio.h>
#define SIZE 10
//定义常量
const int SZ=4;

int main(void)
{
	//修饰数组,表示数组不可修改
    const int marbles[SIZE] = {20,10,5,39,4,16,19,26,31,20};
    //编译报错
//    marbles[0]=1;

    int a[]={1,2,3};
    //修饰指针,表示该指针不能用于修改该指针指向的值,但是指针本身保存的内存地址可以改变
    //函数的形参中使用const修改指针变量表示该函数不会通过该指针修改变量的值
    const int *p=a;
    //编译报错
//    *p=2;
//    p[1]=3;
    //允许修改p自己
    printf("test *(p++)=%d\n",*(++p));

    //表示p2指针的值不能改变,不能指向其他变量
    int * const p2=a;
    //编译报错
//    p2=marbles;
    //可以修改p2指向的变量的值
    *p2=3;
    printf("test *p2=%d\n",*p2);

    //const指针不能赋值给普通指针,否则通过普通指针就可以修改const指针指向的变量值
    //GNU C下只有警告,未报错,C++下不允许这样做
    int *b=p;
    *b=4;
    printf("test *p=%d\n",*p);
    
    return 0;
}

2、volatile关键字

     volatile修饰变量表示该变量是一个容易改变的变量,这时编译器就不会把这个变量放入寄存器或者高速缓存中来提高访问效率,而是直接从内存读写该变量。声明中没有volatile关键字时,编译器默认认为该变量不会改变,会尝试做优化。跟Java中的volatile关键字不同。

3、restrict关键字

     restrict只能用于修饰指针变量或者指针形参,表示该指针是修改指针对应数据的唯一标识;修饰指针变量时,编译器对restrict变量会尝试捷径优化,即对同一个变量的多次操作在编译期合并成一次操作;修饰指针形参时,函数的调用方需要保证该指针是访问目标数据的唯一标识,比 memcpy (void * restrict, const void * restrict, size_t)函数,restrict关键字表示源指针和目标指针不能指向同一个内存地址,注意编译器不会检查是否满足唯一标识条件,如果未满足后果由调用方自负。

4、_Thread_local关键字

     _Thread_local修饰变量表示每个线程都会获得该变量的副本,从被声明时到线程结束变量一直存在。

5、_Atomic关键字

    _Atomic修饰变量表示该变量是一个原子类型变量,即同一时间只有一个线程能访问该变量

四、数组

1、数组初始化

     注意数组使用前必须初始化,未初始化时数组元素的取值为数组元素对应内存上一次操作留下来的值,不一定是0

#include <stdio.h>
#define SIZE 4
#define ARRAY_SIZE(x) (sizeof x)/(sizeof(x[0]))

void printArray(int array[],int);

int main(void)
{
	//数组未初始化时,数组元素元素的取值为实际分配内存的取值,不一定为0
    int no_data[SIZE];
    //数组初始化元素个数与指定值不一致时,编译器自动初始化为0
    int data2[SIZE]={1,2};
    //数组初始化元素个数超过指定值时,编译器会提示警告,多余的会自动丢弃
    int data3[SIZE]={1,2,3,4,5};
    //也可以不指定元素个数,编译器自动加上个数
    int data4[]={1,2,3,4,5,6};
    //C99新语法,指定下标为4的初始化为5,下标为5的初始化为6,下标为6的初始化为8,下标为1的初始化为2,其他的都为0
    int data5[12]={1,[4]=5,6,8,[1]=2};
    //二维数组的初始化,数组在内存中是按照顺序来存储的,先是第一个3个元素的一维数组,
    //然后是第二个3个元素的一维数组,依次类推
    int data6[4][3]={{1,2},{[1]=3},{},{1,2,3,4,5}};

    //不允许对数组变量整体赋值,初始化完成只能对数组单个元素赋值,java允许整体赋值
//    data2=data3;
    //{}的方式只适用于变量初始化,不能用于二次赋值,java同样不允许,只能通过对数组元素赋值
//    data2={1,2,3,4};

    //获取数组长度的两种方法
//    int size=sizeof data4/sizeof(int);
    int size=sizeof data4/sizeof data4[0];
    printf("data4 size-->%d\n",size);

    printf("%2s%14s\n","i", "no_data[i]");
    printf("no_data\n");
    printArray(no_data,ARRAY_SIZE(no_data));
    printf("data2\n");
    printArray(data2,ARRAY_SIZE(data2));
    printf("data3\n");
    printArray(data3,ARRAY_SIZE(data3));
    printf("data4\n");
    printArray(data4,ARRAY_SIZE(data4));
    printf("data5\n");
    printArray(data5,ARRAY_SIZE(data5));
    printf("data6\n");
    for(int i=0;i<4;i++){
    	printf("i=%d ",i);
    	for(int j=0;j<3;j++){
    		printf("%d ",data6[i][j]);
    	}
    	printf("\n");
    }
    return 0;
}

void printArray(int array[],int size){
	 for (int i = 0; i < size; i++)
	        printf("%2d%14d\n", i, array[i]);
}

2、数组边界

     C语言为了保证本身语言本身执行效率,将数组边界的检查交给程序员,C在运行时不会检查数组操作是否越界,而且也没有对应的异常,越界操作数组在语言层面是允许的,但是结果不可控,可能正常运行,也可能因为修改了某个变量导致异常终止。

#include <stdio.h>
#define SIZE 4
int main(void)
{
    int value1 = 44;
    int arr[SIZE];
    int value2 = 88;
    int i;
    
    printf("value1 = %d, value2 = %d\n", value1, value2);
    //C语言编译和运行时都不检查数组越界,不捕获数组越界的异常,对数组越界的操作会转换成对对应内存区域的操作
    //这种行为是因为编译器相信程序员会做越界检查
    for (i = -2; i < 7; i++){
    	//依赖于编译器的行为,在GNU C下,value2和arr[-1]内存地址相同,value1和arr[6]内存地址相同
        arr[i] = 2 * i + 1;
        printf("%2d  %d\n", i , arr[i]);
    }

    printf("value1 = %d, value2 = %d\n", value1, value2);
    
    printf("address of arr[6]: %p\n", &arr[6]);
    printf("address of value1:  %p\n", &value1);
    printf("address of arr[-1]: %p\n", &arr[-1]);
    printf("address of value2:  %p\n", &value2);
   
    return 0;
}

3、数组名与指针

    数组名在C中实际是一个指向数组头元素的指针,可以将数组名赋值给同类型的指针,通过数组下标读写数组元素最终会被编译器转换成对指针的操作。

#include <stdio.h>
#define SIZE 4
int main(void) {
	/*
	 * 一维数组下,数组名就是一个指向数组第一个元素的指针
	 */
	short dates[SIZE] = { 1, 2, 3, 4 };
	double bills[SIZE] = { 1.1, 2.1, 3.1, 4.1 };

	//数组名dates是一个指向数组第一个元素的不可变指针,取值等于&dates[0],跟pti是同类型指针,所以可以赋值给pti
	//因为dates不能二次赋值所以在整个运行过程中dates的值不会改变
	printf("dates=%p,date[0]=%p\n", dates, &dates[0]);
	short * pti = dates;
	double * ptf = bills;

	printf("short size=%d,double size=%d\n", sizeof(short), sizeof(double));
	printf("%23s %15s\n", "short", "double");
	//pti + index 等价于&pti[index],同理 *(pti + index) 等价于 pti[index]
	for (short index = 0; index < SIZE; index++) {
		printf("index      %d: %10p %10p\n", index, &pti[index], &ptf[index]);
		//指针的取值加一个整数,实际是在内存地址增加一个指针指向变量的数据类型的大小,比如pti指向的变量是short
		//short大小是2字节,每次pti加1,pti的内存地址就加2,同时可以得出数组在内存中是顺序存储的
		printf("pointers + %d: %10p %10p\n", index, pti + index, ptf + index);
	}

	//通过指针读写数组
	pti[3] = 4;
	printf("dates[1]=%d,dates[2]=%d,dates[3]=%d\n", pti[1], *(pti + 2),
			dates[3]);


    /*
         * 二维数组下,数组名是一个指向第一个一维数组的指针,这里是指向第一个含有两个int元素的数组的指针
     */
	int zippo[4][2] = { { 2, 4 }, { 6, 8 }, { 1, 3 }, { 5, 7 } };
	//因为[]的优先级高于*,如果不带(),即int *pz[2]表示一个包含两个指向int变量的指针的数组
    int (*pz)[2]=zippo;
    //很容易误解zippo是zippo[0][0]的指针的指针,实际不是,下面的代码因为指针类型不同赋值会报错
//    int **pz2=zippo;
    //zippo[0]是一个指向zippo[0][0]的指针,所以可以赋值给pz3
    int *pz3=zippo[0];

	//zippo指向二维数组的zippo[0]数组,实际就是zippo[0][0]
	//zippo+2 指向zippo[2]数组,实际就是zippo[2][0]
	//zippo[0] 指向zippo[0][0],因为跟zippo指向的都是同一个元素,所以指针变量的值相同
	//zippo[0]+1 指向zippo[0][1]
	//zippo和zippo[0]不能按照普通的指针变量理解,对zippo和zippo[0]的操作都被被编译器转换成对数组的操作
	printf("   zippo = %p,    zippo + 1 = %p\n", zippo, zippo + 1);
	printf("zippo[0] = %p, zippo[0] + 1 = %p,zippo[0][0]=%p\n", zippo[0],
			zippo[0] + 1, &zippo[0][0]);
	printf("  *zippo = %p,   *zippo + 1 = %p\n", *zippo, *zippo + 1);
	//zippo[0]不是普通的指针变量,&zippo[0]会被编译器转换成&zippo[0][0]
	printf("  &zippo[0] = %p\n", &zippo[0]);

	printf("zippo[0][0] = %d\n", zippo[0][0]);
	printf("  *zippo[0] = %d\n", *zippo[0]);
	//注意这里的zippo并非zippo[0][0]的指针的指针,第一次解引用指向zippo[0],第二次解引用指向zippo[0][0]
	printf("    **zippo = %d\n", **zippo);
	printf("      zippo[2][1] = %d\n", zippo[2][1]);
	printf("*(*(zippo+2) + 1) = %d\n", *(*(zippo + 2) + 1));

	//通过指针读写数组
	printf("*(*(pz+2) + 1) = %d\n", *(*(pz + 2) + 1));
	//执行报错Segmentation fault ,所以上面的int **pz2=zippo是不合法的
//	printf("*(*(pz2+2) + 1) = %d\n", *(*(pz2 + 2) + 1));
	printf("*(pz3+1) = %d\n", *(pz3 + 1));

	return 0;
}

4、函数形参中的数组

     因为C语言的数组名实际是一个指针,所以函数形参中可以用同类型的指针代替正常的函数声明

#include <stdio.h>
#define SIZE 10

//int ar[] 等价于 int *ar,下面三种写法等价
//int sum(int ar[], int n);
//int sum(int *, int n);
//函数声明时可以省略参数名,函数定义时不允许
//int sum(int [], int);
//const关键字表明函数不会修改数组内容,如果修改了编译器会报错
int sum(const int ar[], int n);

int sump(int * start, int * end);

int main(void)
{
    int marbles[SIZE] = {20,10,5,39,4,16,19,26,31,20};
    long answer;
    
    answer = sum(marbles, SIZE);
    printf("The total number of marbles is %ld.\n", answer);
    //sizeof marbles这里表示获取整个数组的大小,而非marbles这个变量的大小,所以是10*4共40字节
    printf("The size of marbles is %zd bytes.\n",
           sizeof marbles);
    
    answer = sump(marbles, marbles+SIZE);
    printf("The total2 number of marbles is %ld.\n", answer);
    return 0;
}

//int sum(int ar[], int n)
int sum(const int ar[], int n)
{
    int i;
    int total = 0;
    //因为ar只是一个指针变量,无法通过ar获取该数组的长度,所以数组的长度必须作为第二个参数传递进来
    //否则有数组越界问题
    for( i = 0; i < n; i++)
        total += ar[i];
    //ar实际是一个指针变量,因为是64位系统,所以指针变量占8字节
    printf("The size of ar is %zd bytes.\n", sizeof ar);
    
    return total;
}

//另一种等价的巧妙实现
int sump(int * start, int * end)
{
    int total = 0;

    while (start < end)
    {
        total += *start;
        start++;
    }

    return total;
}

      二维数组比较特殊,声明形参时需要指明第二维的维度,因为数组名本质是一个指针,编译器需要据此知道对应数据对象的长度,如果编译器支持变长数组则忽略此限制。

#include <stdio.h>
#define ROWS 3
#define COLS 4

//下面三种写法等价
void sum2d(int ar[][COLS], int rows);
//void sum2d(int [][COLS], int );
//int sum2d(int (*ar)[COLS], int rows);
int main(void)
{
    int junk[ROWS][COLS] = {
        {2,4,6,8},
        {3,5,7,9},
        {12,10,8,6}
    };
    
    printf("Sum of all elements = %d\n", sum2d(junk, ROWS));
    
    return 0;
}


int sum2d(int ar[][COLS], int rows)
{
    int r;
    int c;
    int tot = 0;
    
    for (r = 0; r < rows; r++)
        for (c = 0; c < COLS; c++)
            tot += ar[r][c];
    
    return tot;
}

5、变长数组

     变长数组只是数组创建时的长度可以动态改变,因为数组不能二次赋值,所以数组一旦创建完成,长度不能再改变。

#include <stdio.h>
#define ROWS 3
#define COLS 4
const int SZ=4;
//int sum2d(int rows, int cols, int ar[rows][cols]);
//这种写法只能用于函数声明,即函数原型中
int sum2d(int rows, int cols, int ar[*][*]);
//这种写法是错误的,因为rows未定义
//int sum2d(int ar[rows][cols],int rows, int cols)
int main(void)
{
    int i, j;
    int rs = 3;
    int cs = 10;
    //普通的静态数组,在编译时就确定内存大小,程序加载时完成内存分配
    int junk[ROWS][COLS] = {
        {2,4,6,8},
        {3,5,7,9},
        {12,10,8,6}
    };
    
    //变长数组是在运行时动态分配
    int varr[rs][cs];  // VLA
    int varr2[rs][SZ];  // VLA
    //变长数组不能在声明时初始化,只能在运行时初始化
//    int varr3[SZ]={1,2,3};
    
    for (i = 0; i < rs; i++)
        for (j = 0; j < cs; j++)
            varr[i][j] = i * j + j;
    
    printf("3x5 array\n");
    printf("Sum of all elements = %d\n",
           sum2d(ROWS, COLS, junk));
    
    
    printf("3x10 VLA\n");
    printf("Sum of all elements = %d\n",
           sum2d(rs, cs, varr));
    
    return 0;
}


int sum2d(int rows, int cols, int ar[rows][cols])
{
    int r;
    int c;
    int tot = 0;
    
    for (r = 0; r < rows; r++)
        for (c = 0; c < cols; c++)
            tot += ar[r][c];
    
    return tot;
}

6、匿名数组(复合字面量)

     字面量是除符号常量外的常量,如int a=5中,5是常量,是int类型的字面量,数组没有对应的字面量。C99新增了复合字面量,相当于一个匿名数组,来表示数组的字面量。

#include <stdio.h>
#define COLS 4
int sum2d(const int ar[][COLS], int rows);
int sum(const int ar[], int n);
int main(void)
{
    int total1, total2, total3;
    int * pt1;
    int (*pt2)[COLS];
    
    //5是int类型的字面量
    int a=5;
    //(int [2]) {10, 20}相当于一个匿名数组,这里表示pt1的复合字面量,该数组只能通过pt1操作
    pt1 = (int [2]) {10, 20};
    pt2 = (int [2][COLS]) { {1,2,3,-9}, {4,5,6,-8} };
    
    total1 = sum(pt1, 2);
    total2 = sum2d(pt2, 2);
    total3 = sum((int []){4,4,4,5,5,5}, 6);
    printf("total1 = %d\n", total1);
    printf("total2 = %d\n", total2);
    printf("total3 = %d\n", total3);
    
    return 0;
}

int sum(const int ar[], int n)
{
    int i;
    int total = 0;
    
    for( i = 0; i < n; i++)
        total += ar[i];
    
    return total;
}

int sum2d(const int ar[][COLS], int rows)
{
    int r;
    int c;
    int tot = 0;
    
    for (r = 0; r < rows; r++)
        for (c = 0; c < COLS; c++)
            tot += ar[r][c];
    
    return tot;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值