关键字
- auto 声明自动变量
- const 定义常量,如果一个变量被 const 修饰,那么它的值就不能再被改变
- extern 声明变量或函数是在其它文件或本文件的其他位置定义
- register 声明寄存器变量
- signed 声明有符号类型变量或函数
- static 声明静态变量,修饰不同的东西含义不同,修饰局部变量,局部变量那段空间不会刷新。修饰其他的,只能/在本地使用。
- typedef 用以给数据类型取别名
- volatile 说明变量在程序执行中可被隐含地改变,编译器不优化,好像是指令重排什么的。
数据类型
- 基本数据类型 int float等
- 枚举类型,枚举变量,enum
- void 类型,类型说明符 void 表示没有值的数据类型,通常用于函数返回值。
- 派生类型:包括数组类型、指针类型和结构体类型。
void 类型
变量为void,需要进行强制转换后,才可以操作,有次面试就栽倒这上面。
变量定义
type variable_list;
int age;
char grade;
int *ptr; 一个指针,其变量类型为整型,也就是其指向的变量类型为整型
int x; // 整型变量x定义
x = 20; // 变量x初始化为20
float pi; // 浮点型变量pi定义
pi = 3.14159; // 变量pi初始化为3.14159
char ch; // 字符型变量ch定义
ch = 'B'; // 变量ch初始化为字符'B'
extern int i; //声明,不是定义
int i; //声明,也是定义
char data[],写习惯了c++老是把[]写到前面哈,emm.
常量
常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量
常量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,也有枚举常量。
整数常量
整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。
整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)。后缀可以是大写,也可以是小写,U 和 L 的顺序任意。
212 /* 合法的 */
215u /* 合法的 */
0xFeeL /* 合法的 */
078 /* 非法的:8 不是八进制的数字 */
032UU /* 非法的:不能重复后缀 */
浮点常量
字符常量
char
定义常量
- 使用 #define 预处理器: #define 可以在程序中定义一个常量,它在编译时会被替换为其对应的值。
- 使用 const 关键字:const 关键字用于声明一个只读变量,即该变量的值不能在程序运行时修改。
#define PI 3.14159
const 数据类型 常量名 = 常量值;
#define 与 const 区别
#define 与 const 这两种方式都可以用来定义常量,选择哪种方式取决于具体的需求和编程习惯。通常情况下,建议使用 const 关键字来定义常量,因为它具有类型检查和作用域的优势,而 #define 仅进行简单的文本替换,可能会导致一些意外的问题。
#define 预处理指令和 const 关键字在定义常量时有一些区别:
替换机制:#define 是进行简单的文本替换,而 const 是声明一个具有类型的常量。#define 定义的常量在编译时会被直接替换为其对应的值,而 const 定义的常量在程序运行时会分配内存,并且具有类型信息。
类型检查:#define 不进行类型检查,因为它只是进行简单的文本替换。而 const 定义的常量具有类型信息,编译器可以对其进行类型检查。这可以帮助捕获一些潜在的类型错误。
作用域:#define 定义的常量没有作用域限制,它在定义之后的整个代码中都有效。而 const 定义的常量具有块级作用域,只在其定义所在的作用域内有效。
调试和符号表:使用 #define 定义的常量在符号表中不会有相应的条目,因为它只是进行文本替换。而使用 const 定义的常量会在符号表中有相应的条目,有助于调试和可读性。
存储类型
- auto
- register
- static
- extern
auto
auto 存储类是所有局部变量默认的存储类。
register
register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个字),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。
static 存储类
static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
全局声明的一个 static 变量或方法可以被任何函数或方法调用,只要这些方法出现在跟 static 变量或方法同一个文件中。
静态变量在程序中只被初始化一次,即使函数被调用多次,该变量的值也不会重置。
extern
extern 存储类用于定义在其他文件中声明的全局变量或函数。当使用 extern 关键字时,不会为变量分配任何存储空间,而只是指示编译器该变量在其他文件中定义。
extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 extern 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。
当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。
运算符优先级
先略,没找到一个更好的
数组定义
emm,c++的<>和c的[]的顺序不一样,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
int a[]
c++写多了,vetor<> n,写习了,面试的时候竟然写成了int[] a,哎,真是个sb啊。
指针数组与数组指针
这两个词语的侧重点是后面,指针数组是一个数组,数组指针是一个指针,数组指针是指向数组的指针。
int a[10];
int (*p)[10]=&a;数组指针
int* p[10] 指针数组。 int*表示
数组名后的方括号 [] 的优先级高于取地址运算符 *,所以就很清楚了
int (*p)[10]说明是一个指针,然后才是什么样的指针,指向数据的指针
int *p[10] 说明是一个数组,然后才是一个数组,一堆指针的数组。哈哈哈
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;
printf("%d", *ptr[3]); // 输出 4
在表达式 *ptr[3] 中,首先应用方括号 [],然后应用取地址运算符 *。因此,这等价于 *((ptr[3])),这会访问数组的第四个元素(在C中,数组索引从0开始),其值为4。
C 传递数组给函数
如果您想要在函数中传递一个一维数组作为参数,您必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,您也可以传递一个多维数组作为形式参数。
-
形式参数是一个指针
-
形式参数是一个已定义大小的数组:
-
形式参数是一个已定义大小的数组:
C 语言静态数组与动态数组
- 静态数组:编译时分配内存,大小固定。
- 动态数组:运行时手动分配内存,大小可变。
静态数组的生命周期与作用域相关,而动态数组的生命周期由程序员控制。
在使用动态数组时,需要注意合理地分配和释放内存,以避免内存泄漏和访问无效内存的问题。
int staticArray[5]; // 静态数组声明
int staticArray[] = {1, 2, 3, 4, 5}; // 静态数组声明并初始化
int array[] = {1, 2, 3, 4, 5};
int length = sizeof(array) / sizeof(array[0]);
以上代码中 sizeof(array) 返回整个数组所占用的字节数,而 sizeof(array[0]) 返回数组中单个元素的字节数,将两者相除,就得到了数组的长度。
动态数组是在运行时通过动态内存分配函数(如 malloc 和 calloc)手动分配内存的数组。
动态数组特点如下:
内存分配:动态数组的内存空间在运行时通过动态内存分配函数手动分配,并存储在堆上。需要使用 malloc、calloc 等函数来申请内存,并使用 free 函数来释放内存。
大小可变:动态数组的大小在运行时可以根据需要进行调整。可以使用 realloc 函数来重新分配内存,并改变数组的大小。
生命周期:动态数组的生命周期由程序员控制。需要在使用完数组后手动释放内存,以避免内存泄漏。
int size = 5;
int *dynamicArray = (int *)malloc(size * sizeof(int)); // 动态数组内存分配
// 使用动态数组
free(dynamicArray); // 动态数组内存释放
int size = 5; // 数组长度
int *array = malloc(size * sizeof(int));
// 使用数组
free(array); // 释放内存
enum枚举
枚举是 C 语言中的一种基本数据类型,用于定义一组具有离散值的常量。,它可以让数据更简洁,更易读。
枚举类型通常用于为程序中的一组相关的常量取名字,以便于程序的可读性和维护性。
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
枚举变量的定义
1、先定义枚举类型,再定义枚举变量
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
2、定义枚举类型的同时定义枚举变量
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
3、省略枚举名称,直接定义枚举变量
enum
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
指针
又爱又恨,指针其实就是一个数字地址,几级指针,就是几次取到真正的数据
指针运算
++ – 都是数据单位长度的哈,也就是加减是一个数据长度单位。int的话,实际地址加减4。
C 指针数组
[]的结合优先级大于*,所以int p[]表示一个数组,数组的元素是指针哈,那么int ** p[]则是一个数组,数组中元素是二级指针。
在我们讲解指针数组的概念之前,先让我们来看一个实例,它用到了一个由 3 个整数组成的数组:
#include <stdio.h>
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i;
for (i = 0; i < MAX; i++)
{
printf("Value of var[%d] = %d\n", i, var[i] );
}
return 0;
}
输出
Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200
一个指向整数的指针数组的声明
int *ptr[MAX];
#include <stdio.h>
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i, *ptr[MAX];
for ( i = 0; i < MAX; i++)
{
ptr[i] = &var[i]; /* 赋值为整数的地址 */
}
for ( i = 0; i < MAX; i++)
{
printf("Value of var[%d] = %d\n", i, *ptr[i] );
}
return 0;
}
输出
Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200
一个指向字符的指针数组来存储一个字符串列表
#include <stdio.h>
const int MAX = 4;
int main ()
{
const char *names[] = {
"Zara Ali",
"Hina Ali",
"Nuha Ali",
"Sara Ali",
};
int i = 0;
for ( i = 0; i < MAX; i++)
{
printf("Value of names[%d] = %s\n", i, names[i] );
}
return 0;
}
Value of names[0] = Zara Ali
Value of names[1] = Hina Ali
Value of names[2] = Nuha Ali
Value of names[3] = Sara Ali
C 传递指针给函数
C 语言允许您传递指针给函数,只需要简单地声明函数参数为指针类型即可。
#include <stdio.h>
#include <time.h>
void getSeconds(unsigned long *par);
int main ()
{
unsigned long sec;
getSeconds( &sec );
/* 输出实际值 */
printf("Number of seconds: %ld\n", sec );
return 0;
}
void getSeconds(unsigned long *par)
{
/* 获取当前的秒数 */
*par = time( NULL );
return;
}
C 从函数返回指针 返回值就是指针
其实就是返回数据是一个指针,几级指针就需要几次获取到真实数据。
int * myFunction()
{
.
.
.
}
另外,C 语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为 static 变量。
现在,让我们来看下面的函数,它会生成 10 个随机数,并使用表示指针的数组名(即第一个数组元素的地址)来返回它们,具体如下:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
/* 要生成和返回随机数的函数 */
int * getRandom( )
{
static int r[10];
int i;
/* 设置种子 */
srand( (unsigned)time( NULL ) );
for ( i = 0; i < 10; ++i)
{
r[i] = rand();
printf("%d\n", r[i] );
}
return r;
}
/* 要调用上面定义函数的主函数 */
int main ()
{
/* 一个指向整数的指针 */
int *p;
int i;
p = getRandom();
for ( i = 0; i < 10; i++ )
{
printf("*(p + [%d]) : %d\n", i, *(p + i) );
}
return 0;
}
函数指针与回调函数
函数指针 侧重点是指针,函数指针是指向函数的指针变量。几级就是几次获取到真实数据。
通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。
函数指针可以像一般函数一样,用于调用函数、传递参数。
函数指针变量的声明:
typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型
#include <stdio.h>
int max(int x, int y)
{
return x > y ? x : y;
}
int main(void)
{
/* p 是函数指针 */
int (* p)(int, int) = & max; // &可以省略
int a, b, c, d;
printf("请输入三个数字:");
scanf("%d %d %d", & a, & b, & c);
/* 与直接调用函数等价,d = max(max(a, b), c) */
d = p(p(a, b), c);
printf("最大的数字是: %d\n", d);
return 0;
}
回调函数
其实就是函数指针作为函数指针作为某个函数的参数,然后在函数中调用。
函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。
简单讲:回调函数是由别人的函数执行时调用你实现的函数。
实例中 populate_array() 函数定义了三个参数,其中第三个参数是函数的指针,通过该函数来设置数组的值。
实例中我们定义了回调函数 getNextRandomValue(),它返回一个随机值,它作为一个函数指针传递给 populate_array() 函数。
populate_array() 将调用 10 次回调函数,并将回调函数的返回值赋值给数组。
#include <stdlib.h>
#include <stdio.h>
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
for (size_t i=0; i<arraySize; i++)
array[i] = getNextValue();
}
// 获取随机值
int getNextRandomValue(void)
{
return rand();
}
int main(void)
{
int myarray[10];
/* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int , 而不是函数指针*/
populate_array(myarray, 10, getNextRandomValue);
for(int i = 0; i < 10; i++) {
printf("%d ", myarray[i]);
}
printf("\n");
return 0;
}
字符串
在 C 语言中,字符串实际上是使用空字符 \0 结尾的一维字符数组。因此,\0 是用于标记字符串的结束。
空字符(Null character)又称结束符,缩写 NUL,是一个数值为 0 的控制字符,\0 是转义字符,意思是告诉编译器,这不是字符 0,而是空字符
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
char site[] = "RUNOOB";
#include <stdio.h>
int main ()
{
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
printf("菜鸟教程: %s\n", site );
return 0;
}
https://www.runoob.com/cprogramming/c-strings.html
结构体
结构体,一堆变量形成组成的一个组合
struct tag {
member-list
member-list
member-list
...
} variable-list ;
如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明,如下所示:
struct B; //对结构体B进行不完整声明
//结构体A中包含指向结构体B的指针
struct A
{
struct B *partner;
//other members;
};
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{
struct A *partner;
//other members;
};
结构体初始化
#include <stdio.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
int main()
{
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
}
结构体的大小可能会受到编译器的优化和对齐规则的影响,编译器可能会在结构体中插入一些额外的填充字节以对齐结构体的成员变量,以提高内存访问效率。因此,结构体的实际大小可能会大于成员变量大小的总和,如果你需要确切地了解结构体的内存布局和对齐方式,可以使用 offsetof 宏和 attribute((packed)) 属性等进一步控制和查询结构体的大小和对齐方式。
结构体定义的几种方式
- 先定义结构体类型然后再定义变量
struck book{
char title[MAXTITL];
char author[AXAUTL];
float value;
};
struck book library;
- 结构体类型和变量同时定义
struck book{
char title[MAXTITL];
char author[AXAUTL];
float value;
}library;
- 省略定义结构体类型,直接定义变量
struck {
char title[MAXTITL];
char author[AXAUTL];
float value;
}library;//该定义方法由于无法记录该结构体类型,所以除直接定义外,不能再定义该结构体类型变量,如果打算多次使用结构模板,就要使用带类型的形式,或者用typedef
C 共用体
共用的,所以一变全变。,定义跟结构体差不多,访问也差不多。
C 位域
C 语言的位域(bit-field)是一种特殊的结构体成员,允许我们按位对成员进行定义,指定其占用的位数。
不会,得好好学学,
https://www.runoob.com/cprogramming/c-bit-fields.html
可变参数
int func(int, ... ) {
.
.
.
}
int main() {
func(2, 2, 3);
func(3, 2, 3, 4);
}
#include <stdio.h>
#include <stdarg.h>
double average(int num,...)
{
va_list valist;
double sum = 0.0;
int i;
/* 为 num 个参数初始化 valist */
va_start(valist, num);
/* 访问所有赋给 valist 的参数 */
for (i = 0; i < num; i++)
{
sum += va_arg(valist, int);
}
/* 清理为 valist 保留的内存 */
va_end(valist);
return sum/num;
}
int main()
{
printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}
排序算法
不熟
https://www.runoob.com/cprogramming/c-sort-algorithm.html
语言实例练习
https://www.runoob.com/cprogramming/c-examples.html