*注: 本页面只记录本人在学习中遇到的容易混淆的C语言笔记,内容并不全面,如有需求请自行查询小甲鱼之类dalao教程

基础

第一个程序 打印 输入

#include <stdio.h>
int main(){
printf("hello world!\n");
int i;
char name[10];
printf("input you age: ");
scanf("%d", &i);
printf("input you name: ");
scanf("%s", name);
printf("age : %d, name : %s\n", i, name);
return 0;
}
// 格式化输出参数
%s // 输出字符串
%d // 输出整型
%c // 输出字符
%f // 输出浮点数
%u // 无符号十进制输出整数
%p // 输出地址

参考:C语言printf格式化输出参数

符号定义常量、标识符、字符串常量

#include <stdio.h>
#include <string.h>
# 符号定义常量
#define NAME "aaa"
#define AGE 123

# 标识符 identifier
- my_name_is

# 字符串写法
char a[6] = {'a','d','m','i','n','\0'};
char a[] = {'a','d','m','i','n','\0'};
char a[] = {"admin\0"};
char a[] = "admin";

# 字符串常量
# 字符串常后多一位\0用于标识字符串结束 也就是%00截断
str = "this is string";
int main(){
printf("Name: %s", NAME);
printf("Age: %d", AGE);
return 0;
}

数据类型、类型修饰符

数据类型

数据类型

使用运算符sizeof()获取数据类型或表达式长度

sizeof(object); //对象
sizeof(type_name); //类型
sizeof object; //对象

类型修饰符

// 用于修饰基本类型
char
short
int
long
long long
signed short int // 有符号 -32768 ~ 32767
unsigned short int// 无符号 0 ~ 65535

数组

一元

#include <stdio.h>
#define NUM 10

int main(int argc, char const *argv[])
{
// 写法
int sum[NUM] ={0}; // 自动填充为0
int sum[NUM] ={1,2,3,4,5}; // 只赋值前5位,后5位自动填充为0
int sum[NUM] ={[2]=3,[5]=6}; // 指定位赋值,其余自动填充为0
int i;
int length = sizeof(sum) / sizeof(sum[0]) // 数组长度
for (i = 0; i< NUM; i++){
printf("%d\n",sum[i]);
}
return 0;
}

二元

多元以此类推

#include <stdio.h>

int main(){
// 写法
float sum1[3][3] = {1.1, 1.2, 1.3, 2.1, 2.2, 2.3, 3.1, 3.2, 3.3};
float sum2[3][3] = {{1.1}, {0.0, 2.2}, {0.0, 0.0, 3.3}};
float sum3[3][3] = {[0][2] = 1.3, [1][1] = 2.2, [2][0] = 3.1};
// 获取数组行列数
printf("sum1总大小:sizeof %lu\n", sizeof(sum1));
printf("sum1第一元大小:sizeof %lu\n", sizeof(sum1[0]));
printf("sum1列数:%lu\n", sizeof(sum1[0])/sizeof(float));
printf("sum1行数:%lu\n", sizeof(sum1)/sizeof(sum1[0]));
printf("float:sizeof %lu\n", sizeof(float));
// 下面为结果
int i, j, n1 = (sizeof(sum1[0]) / sizeof(float)), n2 = (sizeof(sum1) / sizeof(sum1[0]));
for (i = 0; i < n2; i++)
{
for (j = 0; j < n1; j++)
{
printf("%.1f ", sum1[i][j]);
}
printf("\n");
}

for (i = 0; i < n2; i++)
{
for (j = 0; j < n1; j++)
{
printf("%.1f ", sum2[i][j]);
}
printf("\n");
}

for (i = 0; i < n2; i++)
{
for (j = 0; j < n1; j++)
{
printf("%.1f ", sum3[i][j]);
}
printf("\n");
}


}

指针

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

int main()
{
int a = 123;
int b = 345;
int *pa = &a, *pb = &b;
printf("赋值前 ... \npa: %d addr: %p\n", *pa, pa);
printf("pb: %d addr: %p\n", *pb, pb);
a = b;
printf("赋值后 ... \npa: %d addr: %p\n", *pa, pa);
printf("pb: %d addr: %p\n", *pb, pb);
pa = &b;
printf("交换地址后 ... \npa: %d addr: %p\n", *pa, pa);
printf("pb: %d addr: %p\n", *pb, pb);
b = 123;
printf("单独给b赋值 ... \npa: %d addr: %p\n", *pa, pa);
printf("pb: %d addr: %p\n", *pb, pb);
return 0;
}
输出结果

指针数组

存放都是指针的数组

#include <stdio.h>

int main(int argc, char const *argv[])
{

char *str[] = {"ab", "cd", "ef"};

for (int i = 0; i < 3; i++)
{
printf("=======\n");
printf("%s\n", *(str + i)); // str为"ab"的指针,所以取值对指针加减后取
printf("%p\n", *(str + i));
printf("=======\n");
}
return 0;
}
输出结果

数组指针

存放数组指针的指针

#include <stdio.h>

int main(){
int temp[] = {1,5,63};
int(*str)[] = &temp;
int i;

for (i = 0; i < 3 ;i++)
{
printf("=======\n");
printf("%d\n", *(*str + i)); // *str为数组地址,所以*取数组地址的值
printf("%p\n", *str + i);
printf("=======\n");
}
}
输出结果

二维数组与指针

#include <stdio.h>

int main()
{
int num[3][4] = {0}, count = 0;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
num[i][j] = ++count;
// printf("%d\n", num[i][j]);
}
}
printf("int size: %lu\n", sizeof(int));
printf("num size: %lu\n", sizeof(num));
printf("num len: %lu\n", sizeof(num) / sizeof(int));
printf("num addr: %p\n", num);
printf("num[0][0] addr : %p\n", *(num)); // 指针指向数组第一个值
printf("num[0][0] value : %d\n", **(num));
printf("num[1][0] addr : %p\n", *(num + 1)); // 指针加一依次指向每行第一个值
printf("num[1][0] value : %d\n", **(num + 1));
printf("num[2][0] addr : %p\n", *(num + 2));
printf("num[2][0] value : %d\n", **(num + 2));
printf("num[2][1] value : %d\n", *(*(num + 2) + 1));

return 0;
}
输出结果

数组指针与指针

#include <stdio.h>

int main()
{
int num[3][4] = {0}, count = 0;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
num[i][j] = ++count;
// printf("%d\n", num[i][j]);
}
}

int(*p)[4] = num;
printf("num addr: %p\n", p);
printf("num[0][0] addr : %p\n", *p); // 指针地址为数组的第一项地址
printf("num[0][0] value : %d\n", **p);
printf("num[1][0] addr : %p\n", *(p + 1));
printf("num[1][0] addr : %p\n", *(num + 1));
printf("num[1][0] value : %d\n", **(p + 1)); // 与上面二维数组一样
printf("num[2][0] addr : %p\n", *(p + 2));
printf("num[2][0] value : %d\n", **(p + 2));
printf("num[2][1] value : %d\n", **(p + 2) + 1);

return 0;
}
输出结果
结论:
  • 数组的指针以第一维度作为跨度,加减会移动到下一跨度的首位
*(array + i) == array[i] 
*(*(array + i) + j) == array[i][j]
*(*(*(array + i) + j) + k) == array[i][j][k]

例子:

#include <stdio.h>

int main()
{
int num[3][4][5] = {0}, count = 0;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
for (int k = 0; k < 5; k++)
{
num[i][j][k] = ++count;
// printf("num[%d][%d][%d]--> %d --> %p\n", i, j, k, num[i][j][k], &num[i][j][k]);
}
}
}

printf("%d\n", (int)(**num - **(num + 1)));
printf("num addr: %p\n", **num);
printf("num[0][0][0] addr : %p\n", **num);
printf("num[0][0][0] value : %d\n", ***num);
printf("num[1][0][0] addr : %p\n", **(num + 1));
printf("num[1][0][0] value : %d\n", ***(num + 1));
printf("num[2][0][0] addr : %p\n", **(num + 2));
printf("num[2][3][4] value : %d\n", *(*(*(num + 2) + 3) + 4));
printf("num[2][3][4] value : %d\n", num[2][3][4]);
return 0;
}
输出结果

void类型指针

可指向任意类型的指针(需要做好强转的准备)

#include <stdio.h>

int main()
{
int n = 10;
int *num = &n;
char *str = "admin";
void *a;
a = num;
printf("a addr --> %p\n", a);
printf("a value --> %d\n", *(int *)a); // 注意要强转

a = str;
printf("a addr --> %p\n", a);
printf("a value --> %s\n", (char *)a); // 注意要强转
return 0;
}
运行结果

NULL类型指针

用于初始化指针,可防止解引用后错误的指向随机地址

#include <stdio.h>

int main()
{
int *num = NULL;
printf("num addr %d\n", *num);
return 0;
}
运行结果

指向指针的指针

优点:避免重复分配内存,便于修改

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

int main()
{
char *str[5] = {"admin", "password", "test", "user", "audio"};
char **my[4];

for (int i = 0; i < 4; i++)
{
my[i] = &str[i];
}

for (int i = 0; i < 4; i++)
{
printf("------------------\n");
printf("[%d]str field: %s, addr is %p\n", i, str[i], str[i]);
printf("[%d]my field: %s, addr is %p\n", i, *my[i], *my[i]);
}

return 0;
}
运行结果

函数

先定义,再调用

void print_c();
int sum(int a ,int b);
void print_c(){
printf("hello world\n");
}

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

函数与之指针

#include <stdio.h>

void swap(int *a, int *b);
void print_res(int *a, int *b);
void swap(int *a, int *b)
{
int temp;
temp = *b;
*b = *a;
*a = temp;
}

void print_res(int *a, int *b)
{
printf("a:%2d,b:%2d\n", *a, *b);
}

int main()
{
int a = 3, b = 6;
print_res(&a, &b);
swap(&a, &b);
print_res(&a, &b);

return 0;
}
运行结果

函数与数组

#include <stdio.h>

void arr(int a[10]);
void arr(int a[10])
{
printf("in arr sizeof: %lu\n", sizeof a[10]);
a[5] = 10;
}

int main()
{
int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
printf("in main sizeof: %lu\n", sizeof array);
for (int i = 0; i < 10; i++)
{
printf("array[%d]: %d\n", i, array[i]);
}
printf("-------------------\n");
arr(array);

for (int i = 0; i < 10; i++)
{
printf("array[%d]: %d\n", i, array[i]);
}

return 0;
}

运行结果

指针函数

#include <stdio.h>
char *getWord(char);
char *getWord(char a)
{
switch (a)
{
case 'A':
return "Apple\n";
default:
return "None\n";
}
}
int main()
{
char a;
printf("input a char:");
scanf("%c", &a);
printf("%s", getWord(a));
return 0;
}
结果

函数指针

函数会返回地址值

#include <stdio.h>
int scap(int);

int scap(int num)
{
return num * num;
}
int main()
{
int num;
int (*fp)(int);
printf("please input a int number:");
scanf("%d", &num);
fp = scap; // 赋值函数地址
printf("number * number = %d * %d = %d\n", num, num, (*fp)(num));
return 0;
}
结果

函数指针作为参数

#include <stdio.h>
int add(int, int);
int sub(int, int);
int calc(int (*)(), int, int);

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

int sub(int a, int b)
{
return a - b;
}

int calc(int (*fun)(int, int), int a, int b)
{
return (int)(*fun)(a, b);
}

int main()
{
int num1 = 3, num2 = 4;
printf("%d + %d = %d\n", num1, num2, calc(add, num1, num2));
printf("%d - %d = %d\n", num1, num2, calc(sub, num1, num2));
return 0;
}

image-20220814111009721

函数指针作为返回值

#include <stdio.h>
int add(int, int);
int sub(int, int);
int calc(int (*)(int, int), int, int);
int (*select(char))(); // 定义返回值为函数指针

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

int sub(int a, int b)
{
return a - b;
}

int calc(int (*fun)(int, int), int a, int b)
{
return (*fun)(a, b);
}

int (*select(char c))() // 定义返回值为函数指针
{
switch (c)
{
case '+':
return add;
case '-':
return sub;
default:
return 0;
}
}

int main()
{
int num1, num2;
char c;
int (*fp)();
printf("input example 1+2 or 3-2 :\n");
scanf("%d%c%d", &num1, &c, &num2);
fp = select(c);
printf("%d %c %d = %d\n", num1, c, num2, calc(fp, num1, num2));
return 0;
}
结果

全局变量与局部变量

与其他语言一样,局部有限覆盖同名全局。

void fun(){
extern a; // extern 关键字用于修饰外部变量
a++;
}
int a;
int main(){
fun();
}

作用域

四大作用域
  • 代码块作用域:只作用于当前代码块,如函数内部或大括号内部
  • 文件作用域:从声明开始到文件结尾都可访问,可用extern声明已经定义变量,如函数名
  • 原型作用域:函数声明的参数名
  • 函数作用域:用于goto
声明与定义
  • 定义只有一次,声明可以无数次
  • 定义为向编译器申请内存空间并赋随机值,声明为通知编译器该变量已存在无需再申请空间
  • 全局变量既是声明又是定义
链接属性
  • external:外部链接属性,文件作用域中的变量自带属性(默认)
  • internal:内部链接属性,文件作用域中的变量自带属性
  • none:除文件作用域变量外的变量自带属性

*** **可使用static关键字将external的变量改为internal,使其成为私有属性防止外部文件对其进行更改调用

生存期

  • 静态存储期(static storage duration):程序执行期间一直占用内存,执行完毕后销毁,例如函数
  • 自动存储期(automatic storage duration):代码块作用域的变量执行完毕后自动销毁,例如for循环变量,局部变量
auto 自动变量

代码块作用域、自动存储期、空链接属性

auto变量

register 寄存器变量

大多数与自动变量属性相同,代码块作用域、自动存储期、空链接属性

*****注:寄存器变量无法获取变量地址,因为cpu地址不允许获取

static 静态局部变量

使external变量变为internal变量,多文件共享变为单文件独享

typedef 别名

详情看结构体

部分算法(不做深究)

快速排序
#include <stdio.h>
// 快速排序
void quick_sort(int[], int, int);

void arr_p(int[], int);

// 打印数组值
void arr_p(int arr[], int length)
{
for (int i = 0; i < length; i++)
{
printf("%4d ", arr[i]);
}
putchar('\n');
}

void quick_sort(int arr[], int left, int right)
{
/**
* 快速排序实现:
* 1. 取得中值,左值坐标,右值坐标
* 2. 寻找左值大于中值,右值小于中值的值
* 3. 当左值大于右值,进行交换
* 4. 当左值坐标大于右值坐标,重新排序左右数组
*/
int l = left, r = right, temp, middle;
middle = (right + left) / 2;
while (l <= r)
{
while (arr[l] < arr[middle])
{
l++;
}
while (arr[r] > arr[middle])
{
r--;
}
if (l <= r)
{
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
l++;
r--;
}

if (l < right)
{
quick_sort(arr, l, right);
}
if (left < r)
{
quick_sort(arr, left, r);
}
}
}

int main()
{
int arr1[] = {14, 2, 66, 457, 8, 7987, 35, 8, 342, 654, 867, 213};
int arr2[] = {14, 2, 66, 457, 8, 7987, 35, 8, 342, 654, 867};
int length = sizeof(arr1) / sizeof(arr1[0]);
int length2 = sizeof(arr2) / sizeof(arr2[0]);

printf("前:");
arr_p(arr1, length);
quick_sort(arr1, 0, length - 1);
printf("后:");
arr_p(arr1, length);
printf("- - - - - - - - - - - - - - - - - -\n");
printf("前:");
arr_p(arr2, length2);
quick_sort(arr2, 0, length2 - 1);
printf("后:");
arr_p(arr2, length2);

return 0;
}

结果

动态内存管理

  • malloc :申请动态内存空间
  • free :释放动态内存空间
#include <stdio.h>
#include <stdlib.h>

int main()
{

int *p = NULL;
p = (int *)malloc(sizeof(int));

if (p == NULL)
{
printf("初始化失败!\n");
exit(-1);
}
printf("请输入一个int型数据:");
scanf("%d", p);
printf("你输入的是%d \n", *p);
free(p);
printf("你输入的是%d \n", *p);

return 0;
}

结果

不及时释放内存容易造成隐式内存泄露和内存块地址丢失

批量申请内存

#include <stdio.h>
#include <stdlib.h>

int main()
{
int *p = NULL, num, i;
printf("请输入需要录入的整数个数:");
scanf("%d", &num);

p = (int *)malloc(num * (sizeof(int)));

if (p == NULL)
{
printf("内存初始化失败!");
exit(-1);
}

for (i = 0; i < num; i++)
{
printf("请输入第%d个整数:", i + 1);
scanf("%d", &p[i]);
}

printf("输入的整数为(%d个):\n", num);

for (i = 0; i < num; i++)
{
printf("%2d ", p[i]);
}
putchar('\n');
free(p);
return 0;
}
结果
其他内存管理函数
  • memchr:查找字符在字符串中位置
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
char *p = NULL;
char str[] = "", c = 'a';

printf("请输入需要查找的字符串和字符(str char):");
scanf("%s %c", str, &c);

p = (char *)memchr(str, c, strlen(str));

if (p == NULL)
{
printf("None!\n");
}
else
{
printf("Extra! addr is %p , index is %ld \n", p, p - str);
}
return 0;
}

结果

  • memset
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define N 10

int main()
{
int *p = NULL;
int i;
p = (int *)malloc(N * sizeof(int));

memset(p, 0, N * sizeof(int));

for (i = 0; i < N; i++)
{
printf("%2d ", p[i]);
}
putchar('\n');
free(p);
return 0;
}

结果

  • calloc :申请并初始化一系列内存空间

calloc与malloc区别

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

#define N 10

int main()
{
int *p = NULL;
p = (int *)calloc(N, sizeof(int));

for (int i = 0; i < N; i++)
{
printf("%d ", p[i]);
}

putchar('\n');
free(p);
return 0;
}

结果与上图等价

  • realloc :重新分配内存空间

***** 注:

  1. 修改的内存空间大小为size字节
  2. 新分配的控件比原来大,数据不变;比原来小,数据可能丢失!

示例:使用realloc持续申请可变大小的堆内存

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

#define flag -1

int main()
{
int num, count = 0, *p = NULL;

do
{
printf("please input a int (input -1 to exit): ");
scanf("%d", &num);
count++;
p = (int *)realloc(p, count * sizeof(int));
p[count - 1] = num;
if (p == NULL)
{
printf("ERROR! No size for your put!\n");
exit(-1);
}
} while (num != flag);

printf("Your input num is :");
for (int i = 0; i < count; i++)
{
printf("%2d ", p[i]);
}

putchar('\n');
free(p);
return 0;
}

结果

内存布局规律

内存地址高低规律

内存段

  • 代码段:存放执行代码,也可能包含常数变量
  • 数据段:存放初始化的全局变量和静态局部变量
  • BSS段:存放未初始化全局变量和静态局部变量
  • 堆(heap):存放进程运行时被动态分配的内存段,大小不固定,需要手动释放
  • 栈(stack):函数执行的内存区域
    • 区别:
      • 堆手动分配,手动释放
      • 栈自动分配,自动释放
    • 生存周期:
      • 堆可相互访问,直到手动释放为止
      • 栈不可相互访问
    • 发展方向
      • 堆从低地址到高地址
      • 栈从高地址到低地址