IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c语言进阶笔记 -> 正文阅读

[C++知识库]c语言进阶笔记

本文是c语言的进阶学习,需要先了解一些预备知识:程序是如何运行的?

生命周期和作用域

生命周期指的是:变量占用内存的时间,只要在内存里,就是活着的。

内存模型:
静态存储区,存放全局变量和静态变量。
动态存储区,堆和栈。
栈,存放函数的参数、返回值和局部变量。
堆,存放程序员管理的变量。

生命周期:
静态变量,包括静态局部变量和静态全局变量,生命周期是整个程序运行期间。

作用域:
静态局部变量,作用域是函数内,但生命周期是整个程序运行区间。

初始化:
全局变量,程序的运行开始前,在main函数之前;
局部变量,程序运行到该语句时。

全局变量
同一程序中,所有函数外的变量,是全局变量;
不同进程中,所有进程外的变量,是环境变量。

内部函数和外部函数

内部函数用static声明,外部函数用extern声明,默认情况下是extern
因此声明函数时加extern和不加extern效果是一样的。
全局变量也是如此,只是全局变量在外部文件引用时,也要加上extern

指针

先说变量,变量的本质是一块内存块的别名,就是给一个内存块取了别名,操作它时会读写内存块中的值,因此变量是内存块和值得组合。
那么指针,指针是一个内存块的首地址,如果是void *,那么就只是一个指针大小,如果是int *,那么就是一个32位的内存块,因此指针变量是内存首地址和内存大小的组合。

理解指针
验证指针的操作方法:

#include<stdio.h>
int main()
{
	int a[3]={1,2,3};
	printf("a:%p\n",a);
	printf("&a:%p\n",&a);
	printf("&a[0]:%p\n",&a[0]);
	printf("&a+1:%p\n",&a+1);
	printf("&a[0]+1:%p\n",&a[0]+1);
	printf("a[3]:%p\n",&a[3]);
	printf("test:%d\n",&a+1-&a);
	return 0;
}
a:0x7ff7b1963f0c
&a:0x7ff7b1963f0c
&a[0]:0x7ff7b1963f0c
&a+1:0x7ff7b1963f18
&a[0]+1:0x7ff7b1963f10
a[3]:0x7ff7b1963f18
test:1

可以看到,对于数组:
a=&a[0]=&a,都是数组首地址;
a+1=&a[0]+1,都是int指针,每次+1前进4个字节;
&a+1,是int
[3]指针,每次前进4*3=12个字节,执行后指向数组最后一个元素的地址。

指针数组和数组指针

概念不懂有一半原因是名字取的不好。
指针数组是:存放指针的数组;
数组指针是:存放数组的指针。
实体都是最后一个词。

指针数组:

int *p[8];

数组指针:

int (*p)[8];

概念不懂的另一个原因是,结合律和顺序不一致,这要写成int [8] *P,不是秒懂!

由于结合律的原因,在int *p[8];中,p会优先和[]结合,因此int *p[8];是存放8个int型指针的数组;而int (*p)[8];中,使用括号让*和p优先结合,因此int (*p)[8];就是存放int[8]的数组的指针(首地址)。

二维数组

二维数组是使用指针数组实现的。对于a[m][n]a[0]代表的是存放第一行数组的指针的指针,因此解引用*a[0]就获得了第一行数组的指针,那么*((*a[0])+0)就是第一个元素的值。

二维数据获取首地址的四种方法:

#include<stdio.h>
int main()
{
	int arr[4][4]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
	int i=0;
	printf("arr+i:%p\n",arr+i);
	printf("arr[i]:%p\n",arr[i]);
	printf("*(arr+i):%p\n",*(arr+i));
	printf("&arr[i]:%p\n",&arr[i]);
	return 0;
}
arr+i:0x7ff7b3bcbed0
arr[i]:0x7ff7b3bcbed0
*(arr+i):0x7ff7b3bcbed0
&arr[i]:0x7ff7b3bcbed0

arr+i是数组指针,+1后指针前进一位;
arr[i]是指针数组,访问第i个元素,存储的是第i行数组的地址;
*(arr+i)是数组指针,访问第i个元素,解除引用,得到值,存放的是第i行数组的地址;
&arr[i]=arr[i],是指针数组。

指针函数和函数指针

指针函数:返回值带指针的函数;

int *fun()

函数指针:指向函数的指针。

int (*fun)()

所有指针双名词组合的词,都是后面的词是实体。

指针函数没啥好说的,返回值是指针类型。
那么函数指针,作为接口,可以传入各种同类型的函数,使用方法如下:

#include<stdio.h>

int (*fun)(int a,int b);

int add(int m,int n)
{
	return m+n;
}

int main()
{
	fun=add;
	printf("add:%d\n",fun(3,4));
	return 0;
}
add:7

值传递和地址传递

值传递:传递值过程中,会创建一个新值,新值的地址和原值地址不一样。
地址传递:传递值的地址,因此引用的是同一个数据,且传递的大小只是指针大小。

递归

如何理解递归?

#include<stdio.h>

int fibonacci(int n) //1,假设地址0x7ff7b3bcbed0
{
	if (n == 1) {
		return 0;
	} else if (n == 2) {
		return 1;
	} else {
		return fibonacci(n-1) + fibonacci(n-2); //2,假设地址0x7ff7b3bcd3b4
	}
}

int main()
{
	printf("%d\n",fibonacci(5));
	return 0;
}

函数在内存中是顺序存储的,因此在执行过程中,首先执行到1(地址:0x7ff7b3bcbed0),然后继续执行,执行到2(0x7ff7b3bcd3b4),此时又跳回地址1(0x7ff7b3bcd3b4),反复如此,只是参数每次都不一样,然后一直执行到边界值1或2就会退出了。

数据类型

char、int、指针、bool这些都是存储一个值用的,那么想存储多个值怎么办?
可以使用数组,但是数组只能存储相同类型的值,那么想存储不同类型的值怎么办?
可以用结构体。

单值存储:char、int、指针、bool;
多值存储:数组;
自定义数据类型:struct、union、enum、位域。

结构体是c语言中用户自定义的数据类型,使用方法如下:

#include<stdio.h>
int main()
{
	struct Mytype{
		int a;
		char b;
	}hello;

	hello.a=3;
	printf("hello.a=%d\n",hello.a);
	printf("hello.b=%d\n",hello.b);
	printf("hello.b=%c\n",hello.b);

	hello.b='f';
	printf("hello.a=%d\n",hello.a);
	printf("hello.b=%d\n",hello.b);
	printf("hello.b=%c\n",hello.b);

	printf("sizeof:%d\n",sizeof(hello));
	return 0;
}
hello.a=3
hello.b=0
hello.b=
hello.a=3
hello.b=102
hello.b=f
sizeof:8

为了节省空间,c语言还存在共用体这种数据类型,也是用户自定义数据类型,但是与结构体不同的是,结构体会将所有成员变量分配空间,而共用体只会分配一份成员变量中占用内存最大的空间。

联合体的使用方法如下:


使用方法如下:

#include<stdio.h>
int main()
{
	union Mytype{
		int a;
		char b;
	}hello;

	hello.a=3;
	printf("hello.a=%d\n",hello.a);
	printf("hello.b=%d\n",hello.b);
	printf("hello.b=%c\n",hello.b);

	hello.b='f';
	printf("hello.a=%d\n",hello.a);
	printf("hello.b=%d\n",hello.b);
	printf("hello.b=%c\n",hello.b);
	
	printf("sizeof=%c\n",sizeof(hello));
	return 0;
}
hello.a=3
hello.b=3
hello.b=
hello.a=102
hello.b=102
hello.b=f
sizeof=4

注意,此时b会覆盖a的值,因为hello里永远只保留一个值。

在日常生活中,常有一些概念是用有序数列组成的,比如一周有7天,分别叫星期一、星期二、…,为了方便这些概念性有序数列的归纳,c语言制作了枚举类型,其性质是成员自增+1。
枚举是定义了一系列概念,这些概念都是用数字表示,定义方法如下:

enum Days{
Mon=1,Tues,Wednes,Thurs,Fri,Sar,Sun
};

这个概念就相当于定义了:

#define Mon 1
#defien Tues 2
...

因此枚举和宏定义是可以互换的,只是枚举强化了封装的概念并加入了类型检测,而宏是散装的且没有类型检测。

枚举的使用方法如下:

#include <stdio.h>
 
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN };
 
void main()
{
    enum DAY yesterday, today, tomorrow;
 
    yesterday = TUE;
    today = (enum DAY) (yesterday + 1); //类型转换
    tomorrow = (enum DAY) 3; //类型转换
    //tomorrow = 3; //错误
 
    printf("%d %d %d \n", yesterday, today, tomorrow); //输出:2 3 3
}

众所周知,数据在内存中是按字节大小读写的,但在嵌入式中,内存空间很奢侈,因此需要按位的大小来存储数据。
位域,是用户自定义的一种数据结构,允许用户将特定大小的位数组成一个域,并含有域名,这样用户就可以按域名来操作位域了。
位域的定义方式如下:

#include<stdio.h>
struct MyType{
	char a:6;
	char b:2;
	char c:7;
}data;

int main()
{
	printf("sizeof:%d\n",sizeof(data));
	return 0;
}
2

其原理是:
当相邻位域的类型相同时,如果其位宽之和小于该类型所占用的位宽大小,那么后面的位域紧邻前面的位域存储,直到不能容纳为止;如果位宽之和大于类型所占用的位宽大小,那么就从下一个存储单元开始存放。

预处理

1.引用文件
文件的引用方式有:

#include<文件名>
#include"文件名"

区别在于,#include<文件名>只会在系统头文件目录中查找文件,#include"文件名"先在系统头文件目录中查找,再去当前目录下查找。

2.宏定义
宏只是文本替换
通过井undef可以指定宏的作用域,如果不指定,作用域就是全局。

#define pi 3.14
...
#undef

带参数的宏替换

3.条件编译

#if 常量表达式
	程序段;
#else
	程序段;
#endif

4.#pragma指令
#pragma指令的作用是设置编译器状态。

//在编译期间输出信息
#pragma message("我是输出")

//只编译一次
#pragma once

//编译头文件到此为止,后面的无需再编译了
#pragma harstop

//设置字节对齐
#pragma pack(2)

//设置警告信息
#pragma warning(disabel:M N)
#pragma warning(ocne:H)
#pragma warnong(error:K)

goto语句

goto语句也称为无条件转移语句。goto语句只能在函数内部进行转移,不能跨越函数。

goto	标号;

其中,标号使用":"进行标识。
样例:

#include<stdio.h>
int main()
{
	int n=100;
	int num=0;
loop:
	num+=n;
	if(n<100 && n>0)
		goto loop;
	return 0;
}

字符数组

#include<stdio.h>
int main()
{
	int i;
	char arr_s[]={"Hello World!"};
	char arr_c[]={'H','e','l','l','o',' ','W','o','r','l','d','!'};
	printf("sizeof(arr_s):%d\n",sizeof(arr_s));
	printf("sizeof(arr_c):%d\n",sizeof(arr_c));
	return 0;
}
sizeof(arr_s):13
sizeof(arr_c):12

可以看到,字符串常量初始化,会自动加上\0,而字符初始化不会带上\0

assert

assert是if…else的一种替代方法,其作用及优点是:
1.简化判断的写法;
2.如果表达式时候假(即为0),那么他先输出一条错误,然后调用abort函数终止程序;
3.可通过宏定义进行屏蔽和启用,易于将开发和发布分开。

缺点是:
1.影响性能;

断言的使用方法:

assert(表达式);

表达式可以是常量、表达式、函数等。

屏蔽断言的方法:

#define NDEBUG

const

编译器通常不为普通const常量分配存储空间,而是将它保存在符号表中,这使它成为一个编译期间的常量,没有了存储与读内存的操作,它的效率也很高。

首先,如下两种方式是等价的:

const int n=5;
int const n=5;

所以,如下两种方式也是等价的,都是指针常量(指向常量的指针):

const int *p;
int const *p;

如果const位于*左侧,那么const就是用来修饰指针所指向的变量的,常量指针,即指针指向常量;如果const位于*右侧,那么const就是修饰指针本身的,常量指针,即指针本身是常量

所以,常量指针(常量性质的指针)定义方法如下:

int *const a=&b;

指向常量的常量指针如下:

const int *const a=&b;

#define和typedef

#define是预处理指令,用于文本替换,将前边的文本替换为后边的文本。
typedef用于给关键字取别名,相应的过程是在编译期间完成的。

#define INT int
typedef short SHORT;

因此预处理都不需要分号作为语句结束
而编译期间的语句都需要分号结束

在给关键字取别名的时候,建议使用typedef,因为#define会出问题:

#define PINT int*
typedef short* PSHORT;

PINT a1,a2;
PSHORT b1,b2;

我们的本意是定义整形指针a1和a2,但这里展开后是这样的int * a1,a1;,因此只定义了a1是指针类型;
而b1和b2则能如期定义出我们想要的指针类型。

typedefine的用法:

//给数组取别名
typedef int arr[4];
arr a1,a2;//此时a1和a2的类型是int[4]

//给函数指针取别名
typedef int (*pfun)(int n);
pfun a;
a=b;

内存管理

#include<stdlib.h>
void *realloc(void *mem_address,unsigned int newsize); 

按newsize大小重新分配一款内存空间,并释放原来空间,返回新内存空间的地址,失败返回NULL。

#include<stdlib.h>
void *malloc(unsigned int num_bytes);

分配指定字节数的内存空间,不进行初始化,并返回内存空间地址,失败返回NULL。

#inlcude<stdlib.h>
void *calloc(unsigned n,unsigned size);

分配n个长度为size的连续内存空间,自动初始化内存空间为0,返回内存空间地址,失败返回NULL。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 10:25:04  更:2022-08-06 10:28:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年3日历 -2024/3/29 14:28:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码