复合字面量(C99)
语法,把类型名放到圆括号里面,后面紧跟一个花括号扩起来的初始化列表。int *a = (int []){1,2};
#include <stdio.h>
struct argv
{
char a[64];
int b;
};
int func(struct argv a)
{
printf("argv.a=%s argv.b=%d\n",a.a,a.b);
}
int main(int argc,char *argv[])
{
struct argv a = {
.a = "chenhongyu",
.b = 24
};
func(a);
func((struct argv){"chenhongyu",24});
return 0;
}
伸缩型数组成员
C99具有一个称为伸缩型数组成员(flexible array member)的新特性。利用这一新特性可以声明最后一个成员是一个具有特殊属性的数组的结构。该数组成员的特殊属性之一是它不立即存在。第二个特殊属性是您可以编写适当的代码使用这个伸缩型数组成员,就像它确实存在并且拥有您需要的任何数目的元素一样。
- 声明一个伸缩型数组成员的规则:
- 伸缩型数组成员必须是最后一个数组成员。
- 结构中必须至少有一个其他成员。
- 伸缩型数组就像普通数组一样被声明,除了它的方括号是空的。
struct flex
{
int count;
double average;
double scores[]; //伸缩型数组成员
};
C99的意图并不是声明 struct flex 类型的变量,而是希望你声明一个指向 struct flex 类型的指针,然后用 malloc() 来分配足够的空间,以储存 struct flex 类型结构的常规内容和伸缩型数组成员所需的额外空间。
例如,假设用 scores 表示一个内含5个 double 类型值的数组:
struct flex *pf; //声明一个指针
pf = (struct flex*)malloc(sizeof(struct flex) + 5 * sizeof(double)); //为结构和数组分配存储空间
现在有足够的存储空间储存 count、average 和一个含有5个 double 类型值的数组。可以用指针 pf 访问这些成员:
pf->count = 5; //设置 count 成员
pt->scores[2] = 18.5; //访问数组成员的一个元素
/*-----------------------------------------------
flexmemb.c -- 伸缩性数组成员(C99新增特性)
-----------------------------------------------*/
#include <stdio.h>
#include <stdlib.h> //提供 malloc()、free() 原型
struct flex
{
size_t count;
double average;
double scores[]; //伸缩性数组成员
};
void showFlex(const struct flex *p);
int main()
{
struct flex *pf1, *pf2;
int n = 5, tot = 0;
//为结构和数组分配存储空间
pf1 = (struct flex*)malloc(sizeof(struct flex) + n * sizeof(double));
pf1->count = n;
for (int i = 0; i != n; ++i)
{
pf1->scores[i] = 20.0 - i;
tot += pf1->scores[i];
}
pf1->average = tot / n;
showFlex(pf1);
n = 9;
tot = 0;
pf2 = (struct flex*)malloc(sizeof(struct flex) + n * sizeof(double));
pf2->count = n;
for (int i = 0; i != n; ++i)
{
pf2->scores[i] = 20.0 - i / 2.0;
tot += pf2->scores[i];
}
pf2->average = tot / n;
showFlex(pf2);
//释放分配内存
free(pf1);
free(pf2);
return 0;
}
void showFlex(const struct flex *p)
{
printf("Scores: ");
for (int i = 0; i != p->count; ++i)
printf("%g ", p->scores[i]);
printf("\nAverage: %g\n", p->average);
}
匿名结构体
- 匿名结构体不会出现重合 重命名的情况
- 有名结构体 名称不能相同 也就是不能重名
//匿名结构体不会出现重名的情况
struct //无名结构体
{
char name[100];
char phone[50];
int num;
};//不能引用 没有任何意义
struct //无名结构体
{
char name[100];
char phone[50];
int num;
}a1,a2,a3;
//有名结构体 重名有问题
//struct X//无名结构体
//{
//
// int num;
//}a1,a2,a3;
//struct X//无名结构体
//{
//
// int num;
//}a1,a2,a3;
void main()
{
}
C 语言 共用体(联合体)union
几个不同的变量共享一段内存的结构,称为“共用体”类型的结构。
定义共用体类型变量的一般形式为:
union 共用体名
{
成员表列
}变量表列;
eg:
union Data
{
int i; //表示不同类型的变量i,ch,f可以存放到同一段存储单元中
char ch;
float f;
}a, b, c; //变量
在地址空间中表示如下图:
地址 | 1000 | 1001 | 1002 | 1003 |
---|---|---|---|---|
int | ||||
char | ||||
float |
以上3个变量在内存中占的字节数不同,但都是从同一地址开始(图中设为1000)存放,也就是使用覆盖技术,后一个数据覆盖了前面的数据。
共用体类型数据的特点:
- (1)同一内存段可以用来存放几种不同类型的成员,但在每一瞬间只能存放其中一个成员,而不是同时存放几个。
union Data
{
int i; //表示不同类型的变量i,ch,f可以存放到同一段存储单元中
char ch;
float f;
}a, b, c; //变量
a.i = 97;
97换成16进制为0x00000061,又因为电脑的存储方式为小端格式,所以在地址中存在的方式如下图
地址 | 1000 | 1001 | 1002 | 1003 |
---|---|---|---|---|
int | 0x61 | 0x00 | 0xC2 | 0x42 |
char | 0x61 | |||
float | 0x61 | 0x00 | 0xC2 | 0x42 |
因为float的存储方式不同,解码时会按照独自的方式计算解码(可自行搜索float类型数据在内存中的存储方式),所以输出为0.00000。
当a.f = 0x61;时,输出入下图:(打印a.ch时,因为0x0不能用字符打印,所以用的整型,效果一样)
地址 | 1000 | 1001 | 1002 | 1003 |
---|---|---|---|---|
int | 0x00 | 0x00 | ||
char | 0x00 | |||
float | 0x00 | 0x00 |
- 2)可以对共用体变量初始化,但初始化表中只能有一个常量。
- 3)共用体变量中起作用的成员是最后一次被赋值的成员,在对共同体变量中的一个成员赋值后,原有变量存储单元中的值就被取代
- 4)共用体变量的地址和它的个成员的地址都是同一个地址。例如:&a.i,&a.ch,&a.f都是同一值。
1.联合体union的基本特性——和struct的同与不同
- union,中文名“联合体、共用体”,在某种程度上类似结构体struct的一种数据结构,共用体(union)和结构体(struct)同样可以包含很多种数据类型和变量。
不过区别也挺明显: - 结构体(struct)中所有变量是“共存”的——优点是“有容乃大”,全面;缺点是struct内存空间的分配是粗放的,不管用不用,全分配。
- 而联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”;但优点是内存使用更为精细灵活,也节省了内存空间。
- 同结构体通过指针访问一样使用“->”,联合体通过指针访问数据 也是通过“->”
枚举类型(enum)
- 如果一个变量只有几种可能的值,则可以定义为“枚举类型”;所谓“枚举”就是把可能的值一一的列举出来,变量的值只限于列举出来的值的范围, 如:
语法:
enum 枚举类型{枚举成员列表};
//其中的枚举成员列表是以逗号“,”相分隔
如:
enum Spectrum{red,black,yellow,blue,white};
- 可以用“枚举类型”声明符号名称来表示int型常量。只要是能使用int型的地方就能够使用枚举类型。注意:C语言中的枚举的一些特性不适合C++;比如c中的枚举变量允许使用++运算符,但是c++中则不允许。
enum Spectrum{red,balck,yellow,blue,white};
0 1 2 3 4
默认情况下:该枚举列表中的常量值分别为:0,1,2,3,4
enum Spectrum{red=10,balck=20,yellow=30, blue=40,white=50};
enum Spectrum{red,balck=22,yellow,blue,white};
则:red=0,black=22,yellow=23,blue=24,white=25;
- 枚举类型也可以是匿名的
- (1)宏定义
#define FALSE 0
#define TRUE 1
但是宏定义有弱点:其定义的只是预处理阶段的名字,在编译器的预处理阶段会进行简单的名字替换,而且不会进行类型的安全检查,其作用域是全局的,因此若程序中有变量true、false,则会被替换。为了避免这样的情况,采用将全部的宏用大写来命名,以区别于正常的代码。
- (2)匿名的enum枚举
enum {FALSE,TRUE}; //FALSE 0, TRUE 1
这里的FALSE,TRUE都是编译时期的名字,编译器会对其进行类型检查,若代码中其他地方有和该名字冲突的,会报错。因此采用匿名的枚举不会有产生干扰正常代码的尴尬。
-(3)采用静态变量
static const int FALSE = 0;
static const int TRUE = 1;
在这里的TRUE,FALSE同样会得到编译器在编译阶段的检查。由于是静态变量,因此其作用域被限制到了本文件内。相比于enum枚举类型,静态变量不仅是编译时期的名字,同时编译器还可能会在代码中为TRUE,FALSE产生实际的数据,这会增加一点存储空间。
C中共享的名字空间
c使用名字空间来标识识别一个名字的程序部分。作用域就是这个概念的一部分。名字相同但具有不同作用域的两个变量不会冲突,但名字相同并在相同作用域中的两个变量会冲突。但是名字空间是分类别的。在一个特定的作用域内的结构标记(struct)、联合标记(union)以及枚举标记共享一个名字空间。普通变量共享一个名字空间。所以结构标记、联合标记以及枚举标记共享的名字空间和普通变量使用的名字空间是不同的。这意味着,可以在同一个作用域内对一个变量和一个标记使用同一个名字,而不会产生冲突。但是不能在同一个作用域内使用名字相同的两个标记或是名字相同的两个变量。
例如在C中下面的语句不会产生冲突:
struct rect{
int x;
int y;
};
int rect;
(在此,其实可以把struct后的那个rect看作是一种新的类型标记,类似于int的一种类型标记,而int rect中的rect是一个变量名)
但是,用两种不同的方式(标记方式和普通变量方式)使用同一个标识符会造成混乱。而且C++中不允许在同一个作用域内对一个变量和一个标记使用同一个名字,因为C++把标记和变量名放在同一个名字空间中(这也算是C++和C的一点不同之处吧)
typedef
C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE:
typedef unsigned char BYTE;
在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如:
BYTE b1, b2;
按照惯例,定义时会大写字母,以便提醒用户类型名称是一个象征性的缩写,但您也可以使用小写字母,如下:
typedef unsigned char byte;
#include <stdio.h>
#include <string.h>
typedef struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} Book;
int main( )
{
Book book;
strcpy( book.title, "C 教程");
strcpy( book.author, "Runoob");
strcpy( book.subject, "编程语言");
book.book_id = 12345;
printf( "书标题 : %s\n", book.title);
printf( "书作者 : %s\n", book.author);
printf( "书类目 : %s\n", book.subject);
printf( "书 ID : %d\n", book.book_id);
return 0;
}
----
书标题 : C 教程
书作者 : Runoob
书类目 : 编程语言
书 ID : 12345
typedef vs #define
- #define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
- typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
- typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
下面是 #define 的最简单的用法:
实例
#include <stdio.h>
#define TRUE 1
#define FALSE 0
int main( )
{
printf( "TRUE 的值: %d\n", TRUE);
printf( "FALSE 的值: %d\n", FALSE);
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
TRUE 的值: 1
FALSE 的值: 0
函数指针和指针函数用法和区别
- 指针函数
定义
指针函数,简单的来说,就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针。
声明格式为:*类型标识符 函数名(参数表)
这似乎并不难理解,再进一步描述一下。
看看下面这个函数声明:
int fun(int x,int y);
这种函数应该都很熟悉,其实就是一个函数,然后返回值是一个 int 类型,是一个数值。
接着看下面这个函数声明:
int *fun(int x,int y);
这和上面那个函数唯一的区别就是在函数名前面多了一个*号,而这个函数就是一个指针函数。其返回值是一个 int 类型的指针,是一个地址。
这样描述应该很容易理解了,所谓的指针函数也没什么特别的,和普通函数对比不过就是其返回了一个指针(即地址值)而已。
指针函数的写法
int *fun(int x,int y);
int * fun(int x,int y);
int* fun(int x,int y);
- 函数指针
定义
函数指针,其本质是一个指针变量,该指针指向这个函数。总结来说,函数指针就是指向函数的指针。
声明格式:类型说明符 (*函数名) (参数)
int (*fun)(int x,int y);
函数指针是需要把一个函数的地址赋值给它,有两种写法:
fun = &Function;
fun = Function;
取地址运算符&不是必需的,因为一个函数标识符就表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。
调用函数指针的方式也有两种:
x = (*fun)();
x = fun();
两种方式均可,其中第二种看上去和普通的函数调用没啥区别,如果可以的话,建议使用第一种,因为可以清楚的指明这是通过指针的方式来调用函数。当然,也要看个人习惯,如果理解其定义,随便怎么用都行啦。
int add(int x,int y){
return x+y;
}
int sub(int x,int y){
return x-y;
}
//函数指针
int (*fun)(int x,int y);
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//第一种写法
fun = add;
qDebug() << "(*fun)(1,2) = " << (*fun)(1,2) ;
//第二种写法
fun = ⊂
qDebug() << "(*fun)(5,3) = " << (*fun)(5,3) << fun(5,3);
return a.exec();
}
-
二者区别
通过以上的介绍,应该都能清楚的理解其二者的定义。那么简单的总结下二者的区别:
###定义不同
指针函数本质是一个函数,其返回值为指针。
函数指针本质是一个指针,其指向一个函数。 -
写法不同
指针函数:int* fun(int x,int y);
函数指针:int (fun)(int x,int y);
可以简单粗暴的理解为,指针函数的是属于数据类型的,而函数指针的星号是属于函数名的。
再简单一点,可以这样辨别两者:函数名带括号的就是函数指针,否则就是指针函数。 -
用法不同
上面已经写了详细示例,这里就不在啰嗦了。
总而言之,这两个东西很容易搞混淆,一定要深入理解其两者定义和区别,避免犯错。