C语言基本语法速览

目录

1.前言

2.基本语法

2.1数据类型

2.2语句 

 空语句

 条件语句

 循环语句

  跳转语句

2.3操作符和表达式

算术操作符和表达式

关系操作符和表达式

逻辑操作符和表达式

位操作符和表达式

赋值操作符和表达式

条件(三元)操作符和表达式

其他操作符

表达式求值

2.4指针 

指针定义

  二级指针 

2.5函数

函数定义        

函数的类型

指针传递

引用传递

 递归

2.6数组

数组的定义

访问数组元素

数组与指针

多维数组

2.7结构体和联合

        结构体(struct)

        联合(union)


1.前言

         C++首先是C语言的超集,大部分C语言代码可以直接在C++中编译运行,并且C++继承了C的许多特性,包括数据类型、运算符、控制结构等。

        但C是主要面向过程而C++是主要面向对象的,即C++主要使用"对象"来设计软件和应用程序。在C++中可以创建类和对象并使用它们来模拟现实世界的情况,这是C所不具备的。同时,C++具有更多数据类型,包括类、结构体、联合体等,此外C++还支持自定义数据类型。除此之外C++还在函数重载,运算符重载,内存管理,异常处理方面具有新功能和特性。

2.基本语法

2.1数据类型

        数据类型主要分为三大类整型、浮点型、指针,在整型中,有字符、短整型、长整型,它们都分为有符号和无符号两种版本,对于各自有着不同的范围。

基本声明

int: 整数类型
float: 单精度浮点数
double: 双精度浮点数
char: 字符类型
bool: 布尔类型(true 或 false)

int myInteger = 10;  
float myFloat = 3.14f;  
double myDouble = 2.71828;  
char myChar = 'A';  
bool myBool = true;
int* myPointer;
int myArray[10];
int* myPointer = nullptr; // 初始化指针为nullptr,表示它不指向任何有效的内存地址

typedef关键字和#define

        typedef 和 #define 都是 C 和 C++ 中用于定义类型别名的预处理指令,它们可以让你为现有的类型创建一个新的名称,这有助于简化代码、提高可读性,并在某些情况下提供类型安全。不过,它们之间有一些重要的区别。

        typedef 是 C 语言的一个关键字,用于为现有的类型定义一个新的名称。它创建的是一个真正的类型别名,因此编译器在编译时会知道这个新名称代表的实际类型。

typedef int Integer;  
  
Integer myVariable = 10;  // 这里的 Integer 实际上就是 int

  #define 是 C 和 C++ 的预处理器指令,它用于定义宏。虽然它也可以用于创建类型别名,但这种方式实际上只是简单的文本替换,预处理器会在编译前将所有的宏名替换为对应的值或表达式。因此,使用 #define 创建的类型别名在编译时并不被视为真正的类型

#define Integer int  
  
Integer myVariable = 10;  // 在编译前,这里的 Integer 会被替换为 int

   枚举类型

枚举类型的定义通常使用enum关键字,后面跟着枚举类型的名称,以及用花括号{}括起来的一组枚举常量。每个枚举常量都有一个与之关联的整数值,如果没有显式地给枚举常量赋值,那么它们的值会按照定义的顺序递增,默认从0开始。

enum Color {  
    RED,  
    GREEN,  
    BLUE  
};

在这个例子中,REDGREENBLUE是枚举常量,它们分别被隐式地赋值为0、1和2。 

     变量储存类型

        在C语言中共有三个地方可以储存变量:普通内存、运行时堆栈、硬件寄存器。在代码块之外声明的变量总是储存在静态内存中,在代码块内部声明的变量自动储存在堆栈之中,自动销毁。而关键字register可以用于自动变量的声明,提示计算机应该储存在硬件寄存器之中。   

链接属性

   static关键字:static关键字有两种用法,第一种是在代码块内部时,它表示声明一个变量的声明周期是整个程序的执行期间,而不是仅在函数调用期间。并且这样的变量只会被初始化一次,并且在函数调用结束后不会被销毁;第二种是在代码块外部,static用于限制变量的连接作用域,使得变量只在定义它的文件内部可见。

    extern 关键字:用于声明一个变量,该变量的定义在别的文件中。它告诉编译器变量的定义存在于其它地方而不是在当前源文件中

// 在一个头文件中  
extern int globalVariable; // 声明一个外部变量  
  
// 在另一个源文件中  
int globalVariable = 100; // 定义外部变量

2.2语句 

 空语句

        空语句本身只包含一个分号,虽然它本身并不执行任何任务,但有时还是有用,它所适用的场合就说语法要求出现一条完整的语句,但并不需要它执行任何任务。

       控制语句

        控制语句用于控制程序的执行流程,如条件语句、循环语句、和跳转语句。

 条件语句

if (x > 0) {  
    // 如果x大于0,执行这里的代码块  
} else {  
    // 否则,执行这里的代码块  
}

 循环语句

while (x < 10) {  
    // 当x小于10时,反复执行这里的代码块  
    x++;  
}  
  
for (int i = 0; i < 5; i++) {  
    // 初始化i为0,当i小于5时执行循环体,每次循环后i自增1  
}

  跳转语句

break; // 跳出最近的循环或switch语句  
continue; // 跳过当前循环的剩余部分,开始下一次迭代  
goto label; // 无条件跳转到程序中标记为label的位置(通常不推荐使用)

2.3操作符和表达式

        C语言包含多种操作符,这些操作符用于执行各种操作,如算术运算、比较、逻辑运算、位运算等。表达式是由操作符和操作数组成的,用于计算一个值或执行一个操作。

以下是一些C语言中常见的操作符和表达式的概述:

算术操作符和表达式

  • 加法 (+): a + b
  • 减法 (-): a - b
  • 乘法 (*): a * b
  • 除法 (/): a / b
  • 取模 (%): a % b // 返回a除以b的余数
  • 自增 (++): a++ 或 ++a // 将a的值加1
  • 自减 (--): a-- 或 --a // 将a的值减1

关系操作符和表达式

  • 小于 (<): a < b
  • 大于 (>): a > b
  • 小于等于 (<=): a <= b
  • 大于等于 (>=): a >= b
  • 等于 (==): a == b
  • 不等于 (!=): a != b

逻辑操作符和表达式

  • 逻辑与 (&&): a && b // 两者都为真时返回真
  • 逻辑或 (||): a || b // 至少有一个为真时返回真
  • 逻辑非 (!): !a // a为假时返回真,a为真时返回假

位操作符和表达式

  • 按位与 (&): a & b
  • 按位或 (|): a | b
  • 按位异或 (^): a ^ b
  • 按位非 (~): ~a
  • 左移 (<<): a << b // 将a的二进制表示向左移动b位
  • 右移 (>>): a >> b // 将a的二进制表示向右移动b位

赋值操作符和表达式

  • 赋值 (=): a = b // 将b的值赋给a
  • 加等 (+=): a += b // 等价于 a = a + b
  • 减等 (-=): a -= b // 等价于 a = a - b
  • 乘等 (*=): a *= b // 等价于 a = a * b
  • 除等 (/=): a /= b // 等价于 a = a / b
  • 模等 (%=): a %= b // 等价于 a = a % b
  • 左移等 (<<=): a <<= b // 等价于 a = a << b
  • 右移等 (>>=): a >>= b // 等价于 a = a >> b
  • 按位与等 (&=): a &= b // 等价于 a = a & b
  • 按位或等 (|=): a |= b // 等价于 a = a | b
  • 按位异或等 (^=): a ^= b // 等价于 a = a ^ b

条件(三元)操作符和表达式

  • 条件操作符 (? :): expression1 ? expression2 : expression3 // 如果expression1为真,则结果为expression2,否则为expression3

其他操作符

  • 逗号操作符 (,): expression1, expression2 // 顺序执行两个表达式,并返回第二个表达式的值
  • 取地址 (&): &variable // 返回变量的地址
  • 取内容 (*): *pointer // 返回指针指向的值
  • sizeof (sizeof): sizeof(variable_or_type) // 返回变量或类型所占用的字节数

表达式求值

表达式的求值遵循运算符优先级和结合性规则。例如,乘法和除法运算符的优先级高于加法和减法运算符,而逻辑运算符的优先级通常低于算术运算符。可以使用括号来改变默认的求值顺序。了解并正确使用这些操作符和表达式是编写有效和高效的C语言程序的关键。

2.4指针 

指针定义

        首先, 指针是一个变量,它存储的是另一个变量的内存地址。通过指针,我们可以间接地访问和操作那个变量。想象一下,指针就像一个箭头,指向了另一个变量的位置,让我们能够找到并操作那个变量。

具体还是用代码来体现

#include <stdio.h>  
  
int main() {  
    // 定义一个整数变量  
    int a = 5;  
      
    // 定义一个指向整数的指针变量,并初始化为变量a的地址  
    int *p = &a;  
      
    // 输出变量a的值  
    printf("The value of a: %d\n", a);  
      
    // 通过指针p修改变量a的值  
    *p = 10;  
      
    // 再次输出变量a的值,可以看到它已经被修改  
    printf("The value of a after modification through pointer: %d\n", a);  
      
    // 输出指针p所指向的地址  
    printf("The address stored in pointer p: %p\n", (void *)p);  
      
    // 输出变量a的地址  
    printf("The address of variable a: %p\n", (void *)&a);  
      
    // 验证指针p确实指向变量a  
    if (p == &a) {  
        printf("Pointer p points to variable a.\n");  
    }  
      
    return 0;  
}

        在这个程序中,我们首先定义了一个整数变量a并初始化为5。然后,我们定义了一个指向整数的指针p,并将a的地址赋给它。通过*p,我们可以访问或修改a的值。在程序中,我们通过指针pa的值从5修改为10。此外,我们还输出了指针p所存储的地址,以及变量a的地址,以验证指针p确实指向了变量a。最后,我们用一个简单的if语句来确认这一点。

        取值和取址操作符 

        取值操作符用于获取指针指向的值。当它被应用在一个指针上时,它会返回指针指向的内存位置上的值。例如:

int a = 10;  
int *p = &a; // p指向a的地址  
int value = *p; // 使用取值操作符获取p指向的值,此时value等于10

        取址操作符用于获取变量的内存地址。当它被应用在一个变量上时,它会返回该变量在内存中的地址。

int a = 10;  
int *p = &a; // 使用取址操作符获取a的地址,并将地址赋给指针p

  二级指针 

        所谓二级指针,即是指针的指针,是一个指向指针的指针,指针变量是一个变量,那么内存中也有属于它的地址,则我们可以声明一个指针来指向它的值。

        牛刀小试

#include <stdio.h>  
  
int main() {  
    // 定义一个整数变量  
    int a = 42;  
      
    // 定义一个指向整数的指针  
    int *p = &a;  
      
    // 定义一个指向整数指针的指针(指针的指针)  
    int **pp = &p;  
      
    // 使用指针的指针访问整数变量  
    printf("The value of a: %d\n", **pp);  
      
    // 修改指针所指向的整数值  
    *p = 50;  
      
    // 再次使用指针的指针访问修改后的整数变量  
    printf("The value of a after modification: %d\n", **pp);  
      
    // 输出指针p的地址  
    printf("The address of pointer p: %p\n", (void *)p);  
      
    // 输出指针的指针pp所指向的地址,即指针p的地址  
    printf("The address stored in pointer-to-pointer pp: %p\n", (void *)pp);  
      
    // 输出指针的指针pp所指向的指针p所指向的地址,即变量a的地址  
    printf("The address pointed to by the pointer pointed to by pp: %p\n", (void *)*pp);  
      
    // 输出变量a的地址,验证上面的地址是否一致  
    printf("The address of variable a: %p\n", (void *)&a);  
      
    return 0;  
}

        在这个例子中,pp 是一个指针的指针,它存储了 p 的地址。通过 **pp,我们可以间接地访问 a 的值。当我们修改 *p 的值时,**pp 也会反映出这个变化,因为它们都指向同一个变量 a。指针的指针在函数参数中特别有用,特别是当你需要传递一个指针给函数,并且该函数需要修改这个指针的指向时。例如,你可以使用指针的指针来交换两个指针的地址。

2.5函数

函数定义        

        首先,函数是一个代码块,它执行特定的任务并可能返回一个值。函数使得代码更易于理解、重用和维护。

       函数一般形式如下

return_type function_name(parameter list) {  
   body of the function  
}
  • return_type:函数返回的数据类型。如果函数不返回任何值,则使用关键字void
  • function_name:函数的名称。
  • parameter list:函数参数列表,由逗号分隔。

一个返回整数的函数

int add(int a, int b) {  
   int sum = a + b;  
   return sum;  
}

不返回任何值的函数

void displayMessage() {  
   printf("Hello, world!\n");  
}

函数调用

int result = add(5, 3);  
printf("The result is: %d\n", result);

函数的类型

        用户定义的函数、库函数(printf,scanf等)、回调函数(作为参数传递给其它函数的函数,在需要时被调用)

函数的参数传递

        在C和C++中,参数通常是通过值传递的。这意味着当你将一个值传递给函数时,实际上是传递了该值的一个副本,原始值保持不变。如果你想修改原始值,你需要使用指针。

#include <stdio.h>  
  
// 函数声明,使用值传递  
void increment(int x);  
  
int main() {  
    int a = 5;  
      
    // 调用increment函数,传递变量a的值  
    increment(a);  
      
    // 输出变量a的值,预期输出仍然是5,因为increment函数内部修改了x的副本  
    printf("a = %d\n", a);  
      
    return 0;  
}  
  
// 函数定义  
void increment(int x) {  
    // 这里修改的是x的副本,而不是main函数中的a  
    x = x + 1;  
    printf("Inside function, x = %d\n", x);  
}

输出结果将是

Inside function, x = 6, a = 5

指针传递

        当使用指针传递时,函数接收的是变量的地址,而不是变量的副本。这样,函数就可以通过指针来访问和修改原始变量的值。

        

#include <stdio.h>  
  
// 函数声明,使用指针传递  
void increment(int *p);  
  
int main() {  
    int a = 5;  
      
    // 调用increment函数,传递变量a的地址  
    increment(&a);  
      
    // 输出变量a的值,预期输出是6,因为increment函数通过指针修改了原始变量a的值  
    printf("a = %d\n", a);  
      
    return 0;  
}  
  
// 函数定义  
void increment(int *p) {  
    // 这里通过指针p直接访问和修改main函数中的a  
    *p = *p + 1;  
    printf("Inside function, *p = %d\n", *p);  
}

输出结果

Inside function, *p = 6  
a = 6

引用传递

        C语言本身不支持引用传递,这是C++的一个特性。在C++中,引用传递允许函数直接修改调用函数中变量的值。

#include <iostream>  
  
// 函数声明,使用引用传递  
void increment(int& ref);  
  
int main() {  
    int a = 5;  
      
    // 调用increment函数,传递变量a的引用  
    increment(a);  
      
    // 输出变量a的值,预期输出是6,因为increment函数通过引用直接修改了原始变量a的值  
    std::cout << "a = " << a << std::endl;  
      
    return 0;  
}  
  
// 函数定义  
void increment(int& ref) {  
    // 这里通过引用ref直接访问和修改main函数中的a  
    ref = ref + 1;  
    std::cout << "Inside function, ref = " << ref << std::endl;  
}

输出结果

Inside function, ref = 6  
a = 6

        在这个C++例子中,increment 函数通过引用 ref 修改了 main 函数中变量 a 的值。注意,在C++中,使用引用传递时,函数参数前需要加上&符号。

 递归

        递归是指一个函数在其定义中直接或间接调用自身的方法。递归的基本思想是将一个复杂的问题分解为与原问题相似的、规模较小的问题来求解。

        递归通常用来解决以下问题

  1. 分治问题:将一个大问题分解为若干个小问题,然后逐个解决这些小问题,最后将它们的解决方案合并起来得到大问题的解决方案。这类问题往往具有天然的递归结构,例如汉诺塔、斐波那契数列等。
  2. 树形结构问题:树形结构问题通常具有递归性质,因为树形结构可以自然地分解为子树。例如,遍历二叉树、计算树的深度等。
  3. 回溯问题:回溯算法是一种通过搜索所有可能的候选解来找出所有解的算法。当候选解被确认不是一个解时,回溯算法会通过在上一步进行一些变化来丢弃该解,即“回溯”。这类问题也可以使用递归来实现,例如八皇后问题、图的着色问题等。

牛刀小试

def factorial(n):  
    if n == 0:  
        return 1  
    else:  
        return n * factorial(n-1)

        在这个例子中,factorial 函数通过递归调用自身来计算 n 的阶乘。当 n 等于 0 时,递归终止并返回 1。否则,函数返回 n 乘以 n-1 的阶乘,从而实现了阶乘的计算。

        用递归时需要注意的问题:

  1. 递归必须有明确的终止条件,否则会导致无限递归,最终导致栈内存溢出。
  2. 递归的层次不宜过深,否则可能导致栈空间不足,引发栈溢出错误。
  3. 递归算法通常具有较高的时间复杂度,因此在处理大规模数据时可能不太适用。

2.6数组

        数组是C和C++编程中一种非常重要的数据结构,它允许我们存储相同类型的多个元素在一个连续的内存块中。数组中的每个元素都可以通过其索引来访问,索引通常从0开始,到数组长度减1。

数组的定义

在C和C++中,数组可以通过以下方式定义:

// 定义一个包含5个整数的数组  
int myArray[5];  
  
// 初始化一个包含5个整数的数组  
int initializedArray[5] = {1, 2, 3, 4, 5};  
  
// 初始化一个包含部分整数的数组,其余元素将自动初始化为0  
int partiallyInitializedArray[5] = {1, 2}; // 等价于 {1, 2, 0, 0, 0}  
  
// C++11及以后版本中,可以使用列表初始化语法  
int listInitializedArray[5] = {1, 2, 3, 4, 5};  
  
// 定义一个字符数组(也就是字符串)  
char str[10] = "Hello"; // 字符串"Hello"加上一个空字符'\0'

访问数组元素

int value = myArray[2]; // 访问数组中的第三个元素(索引为2)  
myArray[2] = 10; // 将数组中的第三个元素设置为10

数组的长度是固定的,一旦定义后不能更改。在C语言中,如果你想知道一个数组的长度,你需要自己保存这个信息,或者使用sizeof操作符:

int length = sizeof(myArray) / sizeof(myArray[0]); // 计算数组长度

数组与指针

        数组名的本质:在大多数情况下,数组名其实可以看做是一个指向数组第一个元素的常量指针。例如,有一个int arr[10],那么arr就是一个指向int类型的指针,它指向arr[0]。通过这层关系,我们可以通过指针算术来遍历数组。

       指针与数组索引:使用指针访问数组元素与使用数组索引访问元素在功能上是等效的。例如,ptr[i]arr[i]是等价的,它们都访问数组中索引为i的元素。但大多数情况来说,指针的效率高于索引的效率。

        数组作为函数参数:当数组作为函数参数传递时,它实际上已经退化为指针。这以为着在函数内部,你无法知道原始数组的大学,因此在函数内部处理数组时,通常需要指针。下面是传递数组给函数时,声明函数的方法

        1、声明一个指向数组第一个元素的指针,指针的类型应该与数组元素的类型相匹配。

void printArray(int* arr, int size) {  
    for (int i = 0; i < size; ++i) {  
        printf("%d ", arr[i]);  
    }  
    printf("\n");  
}
#调用这个函数时,你可以传递数组名和数组的大小
int myArray[] = {1, 2, 3, 4, 5};  
printArray(myArray, sizeof(myArray) / sizeof(myArray[0]));

 2、使用数组形参,虽然不推荐这样做,因为这会导致函数形参大小不固定,但技术上你仍然可以在函数原型中声明一个固定大小的数组。然而,这种做法限制了函数只能接受特定大小的数组,而且实际上仍然是通过指针来传递的。

void printArray(int arr[], int size) {  
    // 与上面的函数相同  
}

3、 在C++中,你可以使用模板来编写一个能够接受任意大小数组的函数。

template<typename T, std::size_t N>  
void printArray(const T (&arr)[N]) {  
    for (std::size_t i = 0; i < N; ++i) {  
        std::cout << arr[i] << ' ';  
    }  
    std::cout << '\n';  
}

        数组与指针区别:数组在创建时,编译器将为数组声明的元素数量保留内存空间,然后再创建数组名,它的值是一个常量,指向第一个元素的位置。当声明一个指针时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。

多维数组

        C语言中的多维数组是一个非常重要的概念,它允许你存储和操作二维或更高维度的数据集合。多维数组可以看作是数组的数组,也就是说,它的每个元素都是一个数组。二维数组可以看作是一个表格,它包含多行和多列。每行和每列的大小在声明时都需要指定。

二维数组:

int matrix[3][4];  // 声明一个3行4列的整数二维数组  
matrix[0][0] = 1;  // 设置第一行第一列的元素为1  
matrix[1][2] = 5;  // 设置第二行第三列的元素为5  
// ... 以此类推

在这个例子中,matrix 是一个3x4的数组,也就是说,它有3行和4列。你可以通过两个索引来访问它的元素:第一个索引表示行,第二个索引表示列。此外,在C语言中,二维数组matrix[3][4]的索引是从0开始的,而不是从1开始。因此,matrix[3][4]实际上是一个无效的数组访问,因为它超出了数组的边界。

多维数组: 多维数组的概念可以扩展到三维或更高维度。例如,一个三维数组可以看作是一个立方体,它有多个“层”或“页面”,每个页面都是一个二维数组。

int cube[2][3][4];  // 声明一个2层3行4列的整数三维数组  
cube[0][1][2] = 20; // 设置第一层第二行第三列的元素为20  
// ... 以此类推

在这个例子中,cube 是一个2x3x4的数组,它有2个“页面”,每个页面是一个3x4的二维数组。

初始化多维数组 :

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

指针:在多维数组中,数组名实际上是一个指向数组第一个元素的指针。对于二维数组,数组名是一个指向一维数组的指针,这些一维数组又包含指向实际元素的指针。理解这一点对于掌握多维数组和指针的交互非常有帮助。

指针数组和数组指针:在C语言中,指针数组(array of pointers)和数组指针(pointer to array)是两个不同的概念,尽管它们的名称可能听起来相似,但它们的用途和内存布局是不同的。

        指针数组是一个包含多个指针的数组,每个指针可以指向不同的内存位置或对象。指针数组通常用于存储指向不同类型对象或同类型对象的多个指针。

// 示例:一个包含3个整数指针的指针数组  
int *ptr_array[3];  
  
// 初始化指针数组  
int a = 10;  
int b = 20;  
int c = 30;  
ptr_array[0] = &a;  
ptr_array[1] = &b;  
ptr_array[2] = &c;  
  
// 通过指针数组访问整数值  
printf("%d\n", *ptr_array[0]); // 输出:10  
printf("%d\n", *ptr_array[1]); // 输出:20  
printf("%d\n", *ptr_array[2]); // 输出:30

        数组指针是一个指向数组的指针,它本身不是一个数组,而是指向一个固定大小的数组。数组指针通常用于函数参数中,以传递数组而不仅仅是数组的首元素地址。

// 示例:一个指向包含3个整数的数组的指针  
int (*array_ptr)[3];  
  
// 初始化数组指针  
int my_array[3] = {1, 2, 3};  
array_ptr = &my_array;  
  
// 通过数组指针访问数组元素  
printf("%d\n", (*array_ptr)[0]); // 输出:1  
printf("%d\n", (*array_ptr)[1]); // 输出:2  
printf("%d\n", (*array_ptr)[2]); // 输出:3

2.7结构体和联合

        C语言中的结构体(struct)和联合(union)是两种重要的复合数据类型,它们允许我们将多个不同类型的数据封装成一个单独的数据类型。

        结构体(struct)

        结构体是一种复合数据类型,可以包含不同类型的数据项。这些数据项可以是基本数据类型(如 int、float、char 等),也可以是其他结构体类型。结构体提供了一种将多个相关的数据项组合成一个单独实体的方式。

        定义结构体

        在C语言中,你可以使用 struct 关键字来定义结构体。例如:

struct Student {  
    char name[50];  
    int age;  
    float score;  
};

        在这个例子中,我们定义了一个标签(结构体名称)为 Student 的结构体,它包含了一个字符数组 name(用于存储学生的名字),一个整数 age(用于存储学生的年龄),和一个浮点数 score(用于存储学生的分数)。

        使用结构体:通过声明结构体变量来使用结构体,通常有两种主要方式:第一种是在结构体定义时就声明,如

struct Student {  
    char name[50];  
    int age;  
    float score;  
} student1, student2; // 在定义结构体时声明变量

第二种是在其他位置使用结构体类型来声明 

struct Student student1;

        结构体初始化 :

struct Student student1 = {"张三", 20, 90.5};

 结构体的引用(访问):

        访问结构体变量的成员需要使用点运算符(.)例如:

printf("%s is %d years old and has a score of %.2f\n", student1.name, student1.age, student1.score);

        typedef重命名结构体

        为了简化结构体的使用,可以使用 typedef 为结构体类型定义一个新名称,这样在声明变量时就不需要每次都使用 struct 关键字,这也是编程中常用的方法。例如:

typedef struct Student {  
    char name[50];  
    int age;  
    float score;  
} Student_t; // 使用typedef重命名结构体  
  
// 现在可以直接使用Student_t来声明变量,而不需要写struct Student  
Student_t student5;

        结构体的自引用

        结构体不能直接包含自己类型的成员,因为这将导致无限大小的递归定义。但是,结构体可以包含指向自己类型的指针。这通常用于实现链表、树等数据结构。例如:

struct Node {  
    int data;  
    struct Node* next; // 指向下一个节点的指针  
};

        联合(union)

        联合也是一种复合数据类型,但它与结构体有所不同。在联合中,所有的成员都占用同一块内存区域,也就是说,联合的大小是其最大成员的大小。在任何时候,联合中只有一个成员是有效的。

        定义联合:使用union关键字

union Data {  
    int i;  
    float f;  
    char str[20];  
};

       使用联合

        我们可以像使用结构体一样使用联合。但是需要注意的是,由于联合的所有成员都占用同一块内存,因此当你给联合的一个成员赋值时,其他成员的值可能会发生变化,因为它们的内存位置是重叠的。

union Data data;  
data.i = 10;       // data 的 i 成员被赋值为 10  
printf("%f\n", data.f); // 输出 data 的 f 成员的值,可能是不确定的,因为 f 和 i 占用同一块内存

在这个例子中,虽然我们没有直接给 data.f 赋值,但是由于 f 和 i 占用同一块内存,所以 data.f 的值可能会受到 data.i 赋值的影响。

总的来说,结构体和联合都是C语言中非常有用的复合数据类型,它们允许我们将多个相关的数据项组合在一起,从而更方便地管理和操作这些数据。

  • 32
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值