C语言的数据类型、变量声明和特殊操作符

基本数据类型

C语言支持多种基础数据类型及其变体,具体如下:

类型 用途 大小(位) 范围
char 字符 8 -128 到 127
unsigned char 无符号字符 8 0 到 255
short 短整型 16 -32,768 到 32,767
unsigned short 无符号短整型 16 0 到 65,535
int 整型 32 –2,147,483,648 到 2,147,483,647
unsigned int 无符号整型 32 0 到 4,294,967,295
long 长整型 32 -2,147,483,648 到 2,147,483,647
unsigned long 无符号长整型 32 0 到 4,294,967,295
float 浮点型 32 1.2 × 10⁻³⁸ 到 3.4 × 10³⁸
double 双精度浮点型 64 2.2 × 10⁻³⁰⁸ 到 1.8 × 10³⁰⁸
long double 长双精度浮点型 128 3.4 × 10⁻⁴⁹³² 到 1.2 × 10⁴⁹³²

不同系统中这些数据类型的定义可能有所差异,但以下规则始终成立:

  • short <= int <= long
  • float <= double <= long double

变量声明

变量声明格式为:

int myval, tmp1, tmp2;
unsigned int marker1 = 1, marker2 = 10;
float magnitude, phase;

命名规则:

  • 变量名由字母、数字和下划线组成,首字符必须是字母。
  • 变量名可以使用大写字母,但约定俗成的做法是将大写字母保留给常量名。
  • 下划线开头的变量名通常保留给内部库使用。

常量声明:

#define PI 3.141592654   // 预处理器定义符号常量
const int a;             // 定义只读变量

数组声明与初始化

数组可以用以下形式声明和初始化:

int myarray[10];
unsigned int list[5] = { 10, 15, 12, 19, 23 };
float rdata[128], grid[5][5];
  • 数组索引从 0 开始。例如,list 的索引为 0 到 4。
  • 可以使用循环访问数组:
for( int i = 0; i <= 127; i = i + 1 )
{
  printf ( "%f\n", rdata[i] );
}

注意: C语言对数组边界访问不进行严格检查,越界访问可能导致程序行为异常。

字符数组和字符串:

char s[128];
strcpy( s, "This is a test!" );

字符串以 \0(null字符)作为结束标志,许多C函数依赖此标志确定字符串的末尾。


指针

指针存储变量或数组的地址,其声明方式如下:

int *ptr;   // 定义一个指向整型变量的指针
*ptr = 345; // 通过指针修改地址中的值

取地址操作符:

int tmp;
somefunc( &tmp ); // 获取变量 tmp 的地址,并传递给函数

指针与变量关系:

  • 声明指针形式:*myptr
  • &myvar 表示变量 myvar 的地址
  • *myptr 表示指针 myptr 指向的变量值

数组与指针的关系: 数组名本质上是指向数组首元素的指针。例如:

char s[256];
somefunc( s ); // 实际上传递的是数组首地址

但直接访问数组元素如 s[12],表示索引为 12 的值(即第 13 个元素)。


C语言中的特殊字符

C语言定义了一些特殊字符:

转义字符 描述
\a 报警(响铃)
\b 退格符
\f 换页符
\n 换行符
\r 回车符
\t 水平制表符
\v 垂直制表符
\\ 反斜杠
\? 问号
\' 单引号
\" 双引号
\0NN 八进制字符代码
\xNN 十六进制字符代码
\0 空字符

总结

  • 数据类型:C支持多种基本类型,不同系统可能存在实现差异。
  • 变量声明:可以同时声明多个变量,初始化时可以选择不同格式的常量。
  • 数组与字符串:数组索引从0开始,字符串需要以\0作为结束标志。
  • 指针:指针是C语言的重要特性,可以访问和修改变量地址存储的值,同时与数组密切相关。

C语言中的字符串、结构体和向量操作

字符串的特殊性

  1. 字符串字面量作为指针:
    字符串字面量实际被编译器存储为一个指针。例如:

    char *p;
    p = "Life, the Universe, & Everything!";
    

    在此,p 是指向字符串的指针,p[0] 的值为 'L'。类似地:

    char ch;
    ch = "Life, the Universe, & Everything!"[0];
    

    会将 'L' 存储到变量 ch 中。

  2. 错误的字符串初始化:
    以下操作是错误的:

    char s[128] = "This is a test!";
    

    这种操作会导致未使用的内存浪费,并且可能引发越界访问问题。正确的做法是使用 strcpy 将字符串拷贝到数组中:

    char s[128];
    strcpy(s, "This is a test!");
    
  3. 传递字符串到函数:
    字符串作为参数传递时需特别小心。以下示例展示了如何正确处理字符串:

    /* strparm.c */
    
    #include <stdio.h>
    #include <string.h>
    
    char *strtest( char *a, char *b );
    
    int main ()
    {
      char a[256], b[256], c[256];
    
      strcpy(a, "STRING A: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
      strcpy(b, "STRING B: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
      strcpy(c, "STRING C: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
    
      printf("Initial values of strings:\n");
      printf("   a = %s\n   b = %s\n   c = %s\n\n", a, b, c);
    
      strcpy(c, strtest(a, b));
    
      printf("Final values of strings:\n");
      printf("   a = %s\n   b = %s\n   c = %s\n\n", a, b, c);
    
      return 0;
    }
    
    char *strtest( char *x, char *y )
    {
      printf("Values passed to function:\n   x = %s\n   y = %s\n\n", x, y);
    
      strcpy(y, "NEWSTRING B: abcdefghijklmnopqrstuvwxyz0123456789");
      return "NEWSTRING C: abcdefghijklmnopqrstuvwxyz0123456789";
    }
    

结构体

结构体是C语言中用于存储不同类型数据集合的方式。例如:

/* struct.c */

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

struct person
{
   char name[50];
   int age;
   float wage;
};

void display(struct person);

int main()
{
   struct person m;
   strcpy(m.name, "Coyote, Wile E.");
   m.age = 41;
   m.wage = 25.50f;
   display(m);
   return 0;
}

void display(struct person p)
{
   printf("Name: %s\nAge: %d\nWage: %4.2f\n", p.name, p.age, p.wage);
}
  1. 定义和使用:

    • 使用 struct 定义结构体类型。
    • 使用 struct 关键字和类型名声明变量。
    • 使用点符号访问成员。
  2. 结构体赋值:
    可以直接将一个结构体变量赋值给另一个结构体变量:

    struct person m, n;
    m = n;
    
  3. 结构体数组和嵌套:
    结构体可以组成数组或嵌套其他结构体:

    struct person group[10];
    strcpy(group[5].name, "McQuack, Launchpad");
    
    struct trip_rec
    {
       struct person traveler;
       char dest[50];
       int date[3];
       int duration;
       float cost;
    };
    
    struct trip_rec t1;
    strcpy(t1.traveler.name, "Martian, Marvin");
    
  4. 指针访问结构体成员:
    使用 -> 操作符通过指针访问结构体成员:

    struct person *sptr;
    sptr->age = 50;
    
  5. 联合体 (Union):
    类似结构体,但存储的数据共享同一块内存:

    union usample
    {
       char ch;
       int x;
    };
    

向量操作示例

以下程序演示了如何用结构体实现三维向量的常见操作:

/* vector.c */

#include <stdio.h>
#include <math.h>

#define PI 3.141592654

struct v
{
   double i, j, k;
};

void vadd(struct v, struct v, struct v*);
void vprint(struct v);
void vsub(struct v, struct v, struct v*);
double vnorm(struct v);
double vdot(struct v, struct v);
double vangle(struct v, struct v);
void vcross(struct v, struct v, struct v*);

int main()
{
   struct v v1 = {1, 2, 3}, v2 = {30, 50, 100}, v3;
   double a;

   printf("Sample Vector 1: ");
   vprint(v1);
   printf("Sample Vector 2: ");
   vprint(v2);

   vadd(v1, v2, &v3);
   printf("Vector Add: ");
   vprint(v3);

   vsub(v1, v2, &v3);
   printf("Vector Subtract: ");
   vprint(v3);

   vcross(v1, v2, &v3);
   printf("Cross Product: ");
   vprint(v3);

   printf("\nVector 1 Norm: %f\n", vnorm(v1));
   printf("Vector 2 Norm: %f\n", vnorm(v2));
   printf("Dot Product: %f\n", vdot(v1, v2));

   a = 180 * vangle(v1, v2) / PI;
   printf("Angle: %3f degrees.\n", a);

   return 0;
}

/* 其他函数的实现省略,与上述代码一致 */

特点:

  • 使用结构体表示三维向量。
  • 各函数操作包括向量加减、点积、叉积、范数及向量间的夹角。
  • 演示了如何通过指针修改结构体。

总结

  • 字符串: 理解字符串作为指针的行为及使用 strcpy 初始化数组的重要性。
  • 结构体: 提供了一种组织复杂数据的方式,支持数组、嵌套及指针访问。
  • 向量操作: 通过结构体和函数实现复杂数学运算,使代码简洁易懂。

C语言中的静态变量、特殊声明、类型转换和枚举

静态变量 (static)

局部变量可以使用 static 声明,使其在函数的多次调用之间保留值。例如:

#include <stdio.h>

void testfunc(void);

int main()
{
  int ctr;
  for( ctr = 1; ctr <= 8; ++ctr )
  {
    testfunc();
  }
  return 0;
}

void testfunc(void)
{
  static int v;
  printf("%d\n", 2 * v);
  ++v;
}

输出:

0
2
4
6
8
10
12
14

解析:

  1. static 使变量 v 在多次调用 testfunc 时保留其值。
  2. 默认情况下,static 变量初始化为 0,但最好显式初始化以避免依赖默认值。

其他特殊声明

  1. register 声明:
    提示编译器将变量尽量分配到CPU寄存器中,提高访问速度:

    register int counter;
    

    注意:

    • 现代优化编译器会自动优化寄存器的使用,因此 register 声明很少被使用。
    • 如果无法将变量分配到寄存器中,变量会像普通变量一样存储在内存中。
  2. volatile 声明:
    声明变量的值可能随时被外部因素修改,例如硬件寄存器:

    volatile int clock;
    

    用途:
    用于表示实时数据或与外部设备交互的变量。


类型转换

C语言在许多情况下会自动进行类型转换。例如:

  • char 转换为 int
  • int 转换为 float

注意:

  • 从较大类型转换为较小类型(如 int 转换为 char)可能导致数据丢失或意外错误。
  • 签名类型和无符号类型之间的转换可能导致问题。

显式类型转换:

使用强制类型转换(cast)避免隐式转换问题:

int a;
float b;
b = (float)a;

枚举类型 (enum)

枚举类型用于定义一组具名的常量。例如:

enum day
{
  saturday, sunday, monday, tuesday, wednesday, thursday, friday
};

特点:

  1. 默认从 0 开始递增,saturday = 0sunday = 1,依此类推。

  2. 可以指定自定义值:

    enum temps
    {
      zero = 0, freeze = 32, boil = 220
    };
    
  3. 定义后的枚举类型可以声明变量:

    enum day today = wednesday; // `today` 为枚举类型变量,值为 3。
    

优点:

  • #define 定义常量更整洁。
  • 方便代码的可读性和维护性。

自定义类型 (typedef)

typedef 用于为复杂类型定义别名。例如:

typedef char str[128];

之后,可以使用新类型声明变量:

str name;  // 等价于 `char name[128];`

总结

  • 静态变量 (static):使局部变量在函数间调用时保留值。
  • 特殊声明:
    • register:提示编译器优化寄存器分配,但现代编译器通常无需显式声明。
    • volatile:声明值可能随时变化的变量,用于处理硬件寄存器等场景。
  • 类型转换: 谨慎处理隐式类型转换,建议使用显式转换避免问题。
  • 枚举类型 (enum):定义具名的常量集,提高代码可读性。
  • 自定义类型 (typedef):通过别名简化复杂类型声明,使代码更简洁。
Last modified: Monday, 27 January 2025, 11:13 PM