C 与 C++
静态类型,强类型(无泛型,但支持如 int 到 float 的隐式转换)
C 语言通常用于操作系统、软件、嵌入式开发(如Unix系统、Kindle、OpenGL 等)。
C语言文件后缀为.c
或.h
(头文件)。C++ 是 C 的超集,与C语言最大的区别是支持面向对象编程。一个有效的C程序通常也是一个有效的C++程序。
C++语言文件后缀为.cpp
。
编译
C/C++可以使用相同的编译器
CL
CL是微软的非开源编译器,通常包含在 Visual Studio 的环境包中
GCC
gcc 是 GNU 的开源编译器,其windows版本为minGW
- 下载(选择 x84_win32_seh版本,如在线安装失败也可以直接下载压缩包,但需要配置环境变量到
mingw\mingw64\bin
) - 输入
gcc -v
判断是否安装配置成功 - 通过
gcc c语言文件名
将文件编译为可执行文件
编译多个文件
- 通过指令可以直接编译多个文件,如
gcc a.c b.c -o c.exe
- VSCode可以调用CL/GCC,本身没有编译功能
配置完毕后工作目录下出现.vscode/task.json
,修改后可变为多文件编译:
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: gcc.exe 生成活动文件",
"command": "D:\\C\\x86_mingw64\\bin\\gcc.exe",
"args": [
"-fdiagnostics-color=always",
"-g",
// "${file}",
"${cwd}//*.c",
"-o",
"${fileDirname}\\${fileBasenameNoExtension}.exe"
],
}
]
基础语法
#include //预处理器指令,在实际编译前包含头文件
int main() //主函数
{
/* 注释 */
printf("Hello, W3Cschool! \n");//语句结束符;
return 0;
}
注意多文件处于一个作用域中,因此全局函数/变量名不能重复,如main
只能有一个。
输入和输出
%c 字符
%s 字符串
%d 10进制整数
%x 16进制整数
%f 10进制浮点数
int number;
scanf("%d", &number);
printf("number is %d \n", number);
数据类型
常见数据类型
char 整数类型,1字节(8位)
int 整数类型,4字节
float 单精度浮点数类型,4字节
double 双精度浮点数类型,8字节
void 无,如函数无返回值、函数无参数、指针指向void
- sizeof()
可以获得变量的字节数,如sizeof(100)
返回4 - signed 和 unsigned
float、double总是带符号的,而整形中可手动定义是否带符号。
char默认为无符号,其他整型默认为有符号。当需要变更时,如unsigned int = 10
即可。
常量
字符常量是位于单引号''
内的单个字符,也可以是如'\n'
的特殊字符
字符串常量是位于双引号""
内的一个或多个字符,下面这三种形式所显示的字符串是相同的:
"hello, dear"
"hello, \
dear"
"hello, " "d" "ear"
- 定义常量
常量必须同时完成声明、定义、赋值。
使用#define
或const
定义常量,通常定义为大写字母形式
#define LENGTH 10
const int WIDTH = 5;
int area = LENGTH * WIDTH;
存储类
用于定义变量或函数的作用范围和生命周期
- auto
局部变量默认的存储类
{
int mount;
auto int month;
}
- register
向系统建议(实际是否可以由系统决定)存在寄存器(而非内存)的变量,因此变量最大尺寸为1字节。
该变量没有内存地址,因此不可使用&
操作符。 - static
修饰的局部变量不会在进入作用或离开时候创建或销毁
修饰的全局变量/函数不能被其他源文件引用(外部链接属性变成了内部链接属性)
void fn()
{
static int i = 0;
i++;
printf("%d""\n", i);
}
int main()
{
fn();//1
fn();//2
fn();//3
}
- extern
声明,告诉编译器有这个变量存在,但不会为其分配空间。
对于无法初始化的变量,会在同一作用域中找到另一个同名变量的存储位置(通常用于引用另一个文件中的变量或函数):
# a.c
#include
int main()
{
extern int a;
printf("%d \n",a);
}
# b.c
#include
int a = 200;
在python、js等弱类型语言中,不区分声明和定义,因其总是在赋值时才分配内存空间。但在C语言中,如
extern int a
则只是声明,不分配内存空间;int a
才是定义,分配了4字节空间(如此前无声明则同时做了声明和定义);int a = 1
则是同时进行了声明、定义、赋值。一个变量可以多次声明,但只能定义一次。
此外,全局变量定义时会自动进行初始化。如int a;
会初始化为0,char b;
会初始化为'\0'
指针
指针是一种特殊的数据类型
- 指针的值是目标对象的内存首地址(表现为一个整数,可以用
%d
或%x
打印出来) - 指针的类型是目标对象数据的类型
对指针的加减操作会指向另一个内存地址,因此每次整数的变化大小和该指针指向的数据类型字节数有关:
char value_1 = 'a';
char *test_1 = &value_1;
printf("test_1 from %d to %d \n", test_1, test_1 - 1); //test_1 from 6421823 to 6421822
double *test_2 = 1000;
test_2 = test_2 + 1;
printf("test_2 %d \n", test_2); // 1008
地址 &
返回变量地址,地址为一个int
注意,局部变量超出作用域后即销毁,因此无法在作用域外通过其地址获取值,除非定义局部变量为 static 变量
指针 *
用于定义一个指向另一个变量地址的指针,或将该指针解引用,还原为其值。
指针定义时所用的类型为其指向内容的类型。
未赋值的指针会初始化为一个随机值,通常手动赋值为NULL
。
int main()
{
int a = 4;
int *another_a = NULL;
another_a = &a; /* 'another_a' 现在包含 'a' 的地址 */
a = 222;
printf("a 的值是 %d\n", a);//222
printf("another_a 的值是 %x\n", another_a);//61fdf4
printf("*another_a 是 %d\n", *another_a);//222
}
重复使用*
则为指向指针的指针,同理,解引用时也需要使用多个*
:
int realInt = 9;
int *pointer_1 = &realInt;
int **pointer_2 = &pointer_1;
printf("printf realInt by pointer_2: %d", **pointer_2);//9
传值调用与引用调用
向函数传递参数时,默认为传值调用,修改函数内的形式参数不会影响实际参数。
通过为函数形参添加*
,声明参数为引用类型,则改为引用调用:
#include
void swap(int *x, int *y);
int main ()
{
/* 局部变量定义 */
int a = 100;
int b = 200;
swap(&a, &b);
printf("交换后,a 的值: %d\n", a );//200
printf("交换后,b 的值: %d\n", b );//100
return 0;
}
void swap(int *x, int *y)
{
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 temp 赋值给 y */
return;
}
数组
数组是一个固定大小的相同类型元素的顺序集合。
C中数组会占用连续的内存(类似C#的数组),因此。而JS的数组和Python的列表则是哈希映射。
- 数据仅能在定义时赋值,如
double balance[]={1000.0, 2.0, 3.4, 7.0, 50.0};
。 - 若定义时未进行赋值,则必须声明数组的长度,如
double balance[5];
。未赋值的成员会根据数据类型自动初始化。 - 其他场合下只能对数组中具体成员进行赋值,如
balance[0]=2;
。
double arr2[] = {1.1, 2, 4, 8};
double *pp = arr2;
printf("%f \n", *(pp + 2));//4
数组变量是指向数组第一项的指针,因此可以用指针模拟一个数组。通过比较同一个数组中不同成员的指针地址大小,可以判断成员在数组中的先后关系:
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i, *ptr;
ptr = var;
i = 0;
while ( ptr <= &var[MAX - 1] )
{
printf("Address of var[%d] = %x\n", i, ptr );
printf("Value of var[%d] = %d\n", i, *ptr );
ptr++;
i++;
}
return 0;
}
多维数组
int a[3][4] = {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};
指针数组
在数组名前加*
则为指针数组:
int *pointerArray[2];
int int_1 = 1;
int int_2 = 2;
pointerArray[0] = &int_1;
pointerArray[1] = &int_2;
printf("value of pointerArray 1: %d \n",*pointerArray[0]);//1
printf("value of pointerArray 2: %d \n",*pointerArray[1]);//2
函数返回值
函数不能直接返回一个数组,但可以返回其指针(注意使用static
,使局部变量可以在函数外被访问):
int *array()
{
static int arr[3] = {1, 2, 3};
return arr;
}
int main()
{
int *p;
p = array();
printf("%d : %d \n", p + 0, *(p + 0));
printf("%d : %d \n", p + 1, *(p + 1));
printf("%d : %d \n", p + 2, *(p + 2));
}
字符串
C语言中,字符串是以'\0'
结尾的字符数组(所以字符串实际占用的字节数比strlen
获取的字符串长度多1)。
其中'\0'
称为空字符或结束符(NUL,Null character),可以手工添加到字符数组尾,或者编译器会在初始化时自动添加。
char string[7] = {'R', 'U', 'N', 'O', 'O', 'B'};
printf("string: %s\n", string);//RUNOOB
char string2[] = "RUNOOB";
printf("string2: %s\n", string2);//RUNOOB
字符串数组
由于字符串本身是个字符数组,因此字符串数组就是字符的二维数组:
char stringstring[2][3] = {"aaa", "bbb"};
printf("stringstring %c \n", stringstring[1][1]);//b
printf("stringstring %s \n", stringstring[1]);//bbb
因为字符串是字符的数组,而数组的变量又是个指针,因此可以把字符串作为指针数组的成员,来表示字符串数组:
char *names[] = {
"Zara Ali",
"Hina Ali"
};
printf("%s \n", names[0] ); //Zara Ali