C++基础语法

因为世界是物质的,物质是运动的,所以cpp基础语法也可以分为物质和运动两个部分

物质部分---------------------

数字

变量

整数

由以下几种不同类型,不同类型决定了占用空间(与操作系统有关)

typesize /byteabbr
char1c
short2hd
int4d
unsigned4u
long8l
long long8ll
bool

有的时候可以借助1bit的bool来实现一些功能(实际上是1byte,为了数据对齐)

bool i;

  • 求反 (1-i)
  • 正负(2*i-1)
  • 首位(数组)i*n
  • 顺序升降 a>b ^ i
  • 存在与否 a*cmp+b*cmp

浮点数

typesize/bytestructureabbr
float41 8 23f
double81 11 52lf

规范由IEEE确定

格式

对单精度浮点数而言 structure sign / biased exponent /fraction(碎片)

即 符号位(S) 阶码(E) 尾数(M) v a l u e = ( − 1 ) S ∗ M ∗ 2 E value = (-1)^S*M*2^E value=(1)SM2E

尾数

最大取0b11111111,处理成2,则

m a x = 2 128 = 3.4 E 38 m i n = 2 − 126 = 1.18 E − 38 max=2^{128}=3.4E38 \quad min=2^{-126}=1.18E-38 max=2128=3.4E38min=2126=1.18E38

大小是按跟0的距离

精度计算 2 23 = 8388608 2^{23}=8388608 223=8388608精度为7位左右

阶码

在储存时会进行移码,即实际储存的是原数据+127

即原范围是[0,255] 两端-127 =[-127,128] 00000000,11111111特殊处理->[-126,127]

规格数(normal number): 因为二进制尾数都是以1开始,所以存储时,只存小数点后面的数

非规格数(subnormal number):阶码全为0,隐藏的整数部分为0,用来储存0

特殊数(non-number):阶码全为1,inf和nan

特殊浮点数

在浮点数运算过程中产生,在我们输入时,可以利用这个漏洞

nan : not a number 无效数

内存 : 阶码全1,尾数非全0的表示无效数NaN

ind : indeterminate 不确定数

产生 : 对负数开方,求对数,0.0/0.0,0.0*inf,inf/inf,inf-inf(0/0会报异常)

算术运算:与任何数进行算术运算结果都是nan

关系运算:结果都为0

inf : infinite 无穷

内存 : 阶码全1,尾数全0表示无穷大INF

产生 : 1.0/0.0=inf , -1.0/0.0=-inf ,log(0)

运算与普通数据相同,只不过有的运算会产生nan

判断

我们可以利用math.h中的下面的函数来判断上面几种数

isfinite(x),isnormal(x),isnan(x),isinf(x) (与isfinite不同,isinf会返回±1来区分无穷)

模仿提款系统漏洞

先输入nan将mid感染,然后输入5000000,进入牢房

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
	//零钱提现,经过mid中转
	float change = 100,mid=0,account=100, temp;
	printf("change: %.2f mid: %.2f\n",change,mid );
	scanf("%f",&temp );
	if (temp > change)
	{
		printf("wrong1");
		return 1;
	}
	change -= temp;
	mid += temp;

	printf("change: %.2f mid: %.2f\n", change, mid);
	scanf("%f", &temp);
	if (temp > mid)
	{
		printf("wrong2");
		return 1;
	}
	mid -= temp;
	account += temp;
	printf("mid: %.2f account: %.2f\n", mid, account);
}

数组

数组是一种用于存储相同类型的元素序列的数据结构

int numbers[5]{}; // 声明一个包含5个整数的数组,初始化为0
int numbers[] = {1, 2, 3, 4, 5}; // 自动确定数组大小并初始化元素
int size = sizeof(numbers) / sizeof(numbers[0]); // 计算数组的大小
for (const auto x : numbers) cout << x << " ";

int matrix[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; // 二维数组

静态变量

静态与动态相对,静态为编译时能够确定的,动态为运行时能够确定的

在程序被调用时被执行,在程序销毁时销毁,生命周期不限于函数内,相当于写在函数内的全局变量

可以结合类的构造函数和析构函数

一般为全局共享,也可以设置为私有的

常量

不可以修改的量

const double pi =3.1415926;

字符

编码规范,windows中现行两种,基于ASCII的ANSI和全球通用的UNICODE

ascii

char与字符的映射,如下表

ascii.png

特点是简单,易学,最好记住数字和字母的ascii码范围,C语言基于此字符集

ANSI

英文部分使用ascii编码表,中文一般用GBK或GB2312编码

特点

在前期学习的时候方便使用

利用资源看起来比较小

另外,可以自由的配置不同的字符集,同时也是缺点,不支持不同的字符集

在没有该字符集的区域需要额外下载字符集

UINCODE

在实际windows开发时更适合

因为自windows_NT后,windows所有版本都是由unicode来构建的

所以ANSI在执行时会先转换成unicode字符,效率较低

但在基础cpp学习时不方便

char

c语言基于char的字符串

输入

gets(str);//把一行输入存入str

长度

strlen(str);
sizeof(str);//后者比前者多1 即结尾的'\0'

拷贝

char s1[] = "1234", s2[] = "2345",s3[6]="a";
memcpy(s2, s1,2); //s2="1245 memcpy(目标,源头,字节数);//注意是字节数

strcpy(s3, s1);//就是memcpy
strncpy(s3, s1,6);//需要自己追加\0 n为拷贝的个数
s3[6] = '\0';

连接

char s1[10] = "1234", s2[] = "2345";//s1的大小必须足够装下s2, concatenate
strcat(s1, s2);

比较

char s1[10] = "1234", s2[] = "01234";
int a = strcmp(s1, s2);//返回-1 +1 0,相当于比较大小了
stricmp(s1,s2);//比较两个字符串,不区分大小写 i: ignore
strnicmp(char *s1,char * s2,int n);//比较前n位

查找

char *strchr(char *s,char c)//查找单个字符c
int strcspn(char *s1,char *s2)//查找字符串s2

string

cpp中自带的数据类型,通过类实现

输入

//cin		以空格分割
string word;
while(cin>>word){}
//getline	以换行分割
string s;
getline(cin s)
cin.get();//注意读取上次留下的换行符

初始化

string str1,str2,str3;
//直接初始化
str1 ="hello world!";
str2 ="c++";
str3=str1+str2;
//构造函数
string str(3, '6');
str = string(3,'9');

长度

sizeof(str);//其大小是从heap中动态分配的
str.size(); //不看'\0' cpp中类的数据一般都用这个获取长度

字符与数字转换

to_string(1234);
stoi("1234");
//这里的i是指转换成什么格式,有下面几种可以替换
//i:int l:long f:float d:double ul:unsigned long ...etc都是首写

成员函数

string str{"12345",1,3};//str="234"
//如上,成员函数一般都是操作两个字符串,包含下面五个可选参数
//str1.function(str1_pos,str1_num,str2,str2_pos,str2_num);
//_pos表示位置,_num表示参与位数,数字会优先认为是_num,再试_pos;如果不填就是从0开始的全部
str.compare();//也可以用关系运算符 返回值一样
str.substr();
str.replace();
//下面
str.rfind(snew,_pos);//返回首字符地址 没找到返回 std::string::npos//no possible
str.insert();//第一个参数位放插入位置
str.erase();
str.clear();

const char* cstr=c_str(str);//转化为c风格字符串

wchar_t

unicode用此表示,字符要用L修饰,还需要像如下进行设置才能正常使用

作为了解即可,算法学习用不上,windows编程中有自带的CString类型支持unicode

#include<iostream>
int main()
{
	wchar_t s[] = L"中国";
	wchar_t s1[3];
	wcscpy(s1, s);//另外还支持wcslen,wcscat,wcscmp,wcsstr(查找)
	setlocale(LC_ALL, "chs");
	wprintf(L"%s", s1);
}

指针

指针的不同类型决定的是操作的位数
指针具有类型与指向地址的两个属性
指针只是一种特殊的数据,其大小跟操作系统有关
变量 通过操作系统来控制空间,直接控制值
指针 直接控制内存空间,间接控制值
对结构体返回指针就避免了赋值,节约内存

指针虽然需要类型相对应,但我们可以利用一个中间指针来强制改写地址

int *mid = (int*)&BeAtk;
mid[0] = 0x41fd40;

函数指针

//对于函数最重要的是*参数和返回值的类型*
int add(int a, int b) 
{ 
    return (a + b); 
}
//指针定义,初始化
int (*padd)(int a,int b) {add};//也可写成(int,int)
padd(1,2);//与函数使用方法相同
//对结构体定义指针时,应依次传参

//强制转换
short(*pfadd)(short,short) =(short (*)(short,short))add;
//简化写法    #define typedef一样
using pfadd=short(*)(int,int);
typedef short(*pfadd)(int,int);
Pfadd padd=(Pfadd)add;
//将函数指针作为参数使用
int test(int a,int b,pfadd x)
{ 
    return x(a,b);
}

指针函数就是下面返回值为指针的那个

数组与指针

int a[]{ 1,2,3,4,5 };
int b[2][5]{ 1,2,3,4,5,6,7,8,9,0 };

int* pa[5]{a};
//指针数组:指针为元素的数组 
//数组的本质是指针,指向第一个元素,所以也可赋值成{&a[0]}
int* pb = (int*)b;
//多维数组:取址时 需进行一次类型转化,因为多维数组需要多传递一个信息(行列数),不是纯粹的地址,而是数组指针
int(*ppb)[5] {b};
//数组指针:指向数组的指针  用()来表明是指针 默认处理为数组
//没有引用数组,所以定义'数组引用' 必须打括号

智能指针

//唯一智能指针
//不能其他指向,避免了野指针
std::unique_ptr<int[]> smartptr{ new int[5] {1,2,3,4,5} };
//不能std::unique_ptr<int[]> smartptrcopy{smartptr};
std::unique_ptr<int[]> smartptrA{ std::make_unique<int[]>(5) };//c++14把后面[]去掉后表示初始化为5
smartptr.reset();//释放空间,地址清零	
int *a = smartptr.get();//返回原始指针
a = smartptr.release();//不释放空间,地址清理,返回原始指针
smartptrA = std::move(smartptr);//智能指针之间的数据转化

//共享智能指针
//不能以数组形式访问,赋值与unique大致相同,当最后一个被释放时内存才释放,用reset只会清除自身
std::shared_ptr<int> smartptrshare{ new int[5] {1,2,3,4,5} };
std::shared_ptr<int> smartptrshareA{ smartptrshare };
cout<<smartptrshare.use_count();//返回指针被指向了多少次
smartptrshare.unique();当最后一个调用时返回true c++17已废弃

指针安全

//指针安全
//野指针,悬挂指针, 指针与内存空间释放不同步//大部分程序员是没有对象的野指针
int* p;
{	
	int* a = new int[10];
    p = a;
}
cout << p[2];//a 的生命周期已结束{}内,而内存还在,用智能指针可以解决问题

内存管理

分配内存后一定要释放,不然会导致内存泄漏,即内存被不需要的垃圾充满,从而导致程序崩溃

unsigned size;
cin >> size;

//内存管理 c
int* p1 = (int*)malloc(size * sizeof(int));
//memory allocate 内存分配 dynamic 返回值为void,没有类型,只有一个地址,赋值前应先转化
int* p2 = (int*)calloc(size, sizeof(int));
//c=count ,分配后会将内存清零,但内存占用更大
//分配之后p就可以当作一个有size个元素的数组使用
//内存分配失败时return 0 0也可以写成nullptr(in Cpp)
p2 = (int*)realloc(p2, size);//re-再次
//先搜索附近有没有空位置,没有再往后移,并拷贝之前的数据
free(p1); p1 = 0;
//释放内存,并将指针初始化,下次使用时不会出错

//内存管理 cpp
p1 = new int[size];//用malloc实现,可以用于动态大小的数组
p2 = new int;
delete [] p1;//不用参数
delete p2;
//备份数组
int num[5]{ 1,2,3,4,5 };
int* pnum = new int[5];
//随机分配,地址不确定,而直接int num[100]{}的地址可通过对栈的加减进行确定
memcpy(pnum, num, 5*sizeof(int));//(目的地,原数据,大小(单位为字节))
memset(pnum, 0, 20);//将所有pnum中的数据变成零{char类型都可以

指针类型

//输出与运算都与类型有关,小内存指针操作低位,高位不变
//p++是按4/8为单位自增
unsigned ua{ 1 };//输出类型
int* pua=(int*)&ua ;
*pua = -1;//0xFFFFFFFF
char* pc1 = (char*)pua;
*pc1 = 'a';//0xFFFFFF41
cout << ua;

多级指针

int a[]{ 1,2,3,4,5 };
int* pa = &a[0];
int** ppa = &pa;
*ppa = &a[3];
//定义动态二维数组
int n;
cin >> n;
int** num = new int* [n];
for (int i = 0; i < n; i++)
{
	num[i] = new int[2];
	cin >> num[i][0] >> num[i][1];
}

常量与指针

//常量指针
const int con1 = 514;
const int* pcon1 = &con1;//可以修改指向对象,不能改变对象数据
//指针常量
int var = 114;
int* const pcon2 =&var;//不可修改指向对象,可以修改对象数据
//指向常量的指针常量
const int* const pcon3 = &con1;//啥都不准改,风雨不动安如山
//字符串是常量,其两种定义方法
char str[] = "hello world";
char* pstr1 = (char*)str;
const char* pstr2 = str;

空指针

NULL和nullptr,可能会导致类型推断错误,即调用到并非我们期待的函数

NULL宏定义(void*)0,很多时候被直接处理成0,

nullptr具有类型nullptr_t,会按指针处理

#include <iostream>

void foo(int x) {
	std::cout << "foo(int) called" << std::endl;
}

void foo(char* ptr) {
	std::cout << "foo(char*) called" << std::endl;
}

int main() {
	foo(NULL);  // 会导致类型推断问题,如果是想以指针调用
	return 0;
}

引用

引用是另类指针

引用,给一个变量取不同的名字,后续不可改变引用对象

但必须用实体初始化,指针可以不初始化

引用是一个更接近实体的东西

int a{ 1000 };
int& la{ a };
int *&quote=ptr;//对指针的引用,可以更改指向对象
//对外表明自己是实体,背地里按指针的方式处理问题

右值引用

左值有明确的内存空间,在栈里,能写入,如定义一个变量并赋值

右值临时分配内存空间,在堆里,不能写入,如 1+1

一般左值在等号左边,右值在等号右边 int a[10]; *(a+1)=100;

int && e{100+231};
//与二级指针不同,&&仅为引用右值,不能用来存放引用

组合

自定义

#define version "v1.0"
typedef long long int64;//注意先后顺序
using pfadd=float(*)(int,int);

结构体 联合体

结构体在使用时与int之类的一样,但传递时会申请内存空间再赋值

应该使用指针,只传递一个地址

一堆数据的捆绑

结构体

//内存对齐,sizeof(Role);返回12,依次堆叠,就像俄罗斯方块一样,小的可以进入上面的空位,而大的为封锁空位
typedef struct Role//自定义type
{
	int HP;
	int MP;
	short LV;
}*Prole,*Nbrole;
//自定义结构体,并可同时定义多个指针 Prole与Role*等价
//定义结构体是给编译器看的,不会翻译成汇编代码,而是作为后面翻译的字典
//结构体来表示个体的属性,数组来表示个体

//结构体里面放函数,面向对象,但最好别这么写,面向对象用class定义类
//c++底层也是利用的这个
struct role
{
	int hp;
	int atk;
	
	int act(role* beacter)
	{
		beacter->hp -= atk;
		return beacter->hp;
	}
};
int main()
{
	role a{ 1000,100 }, b{ 1200,80 };
	a.act(&b);
}	

C中的结构体

typedef struct node
{
    ElemType data;
    struct node*next;  //此处的类型定义还未完成,仍需用struct node来调用
}node;

联合体

//共享内存,其大小与最大数据类型大小相同,后面赋值数据直接覆盖前面
union once
{
	short lv;
};
union
{
	int b;
}only;//只用一次的结构体,变量名only来存放,可以嵌套用在结构体中

命名空间

嵌套命名空间

namespace game//数据,函数仓库
{
	int hp(1000);
	int mp(2000);
	int lv(0);
	namespace weapon
	{
		int atk(50);
		int ap(100);
	}
}
using namespace std;//将会将所有的引用进来,浪费内存,定义冲突

全局命名空间

使用全局命名空间::p是为了防止与局部同名标识符发生冲突

int p=100;
int main()
{
    int p=10;
    std::cout<<::p;
}

命名空间的扩展

namespace std
{
	float pi=3.14159;
}
int main()
{
	std::cout << std::pi;
}

声明与定义

//声明在test.h
namespace htd
{
	extern float pi;
	void test();
}
//定义在test.cpp中
# include "test.h"//也可以将声明复制一遍
float htd::pi = 3.14159;
void htd::test() { std::cout << "hello!"; }

未命名的命名空间

仅对本转换单元有效

namespace
{
	void hack()
	{
		std::cout << "Thanks\n";
	}
}
int main()
{
	hack();
}

命名空间的昵称

可用于对于多级命名空间简化

htd::hack::safe();//原本的样子
namespace a = htd::hack;//取昵称
a::safe();//调用的样子

枚举

enum class weapon :int
//默认为int,但使用时必须先先转换
//enumerate枚举提高安全性,可读性 
{
	normal = 1, armA = normal,//值为递增关系,插入时自动更改
	high, armB = high,
	epic, armC = epic,
	legend, armD = legend,
	myth, armE = myth
};
weapon sword{ weapon::normal };
//赋值方式
short dif = (int)sword - (int)arch;

运动部分---------------------

运算

优先级

单目>算术>位移>判断(不等>等)>位运算>逻辑>条件>赋值…

具体如下

operc__.png

单目运算

  • 读取数据和函数

    :: . -> ()

    sizeof();//获取占用内存string 32 vector 24

算术运算

隐式类型转换

int<long<longlong<float<double<longdouble tip:short,char都转化为int

unsigned>signed

时间复杂度

+ - 1tick * 3tick /30tick

-其实就是补码加法,在循环条件中,能用乘法就不要用除法

关系运算

3.14 == pi ;

以此种规范写,可以防止一时疏忽从而没区分 = 与 ==

位运算

& | ~ 与或非 << >>左右移

#include<bitset>
int a = 1000;
cout << (a >>= 4)<<endl;
//左右移 乘除2^n unsigned 都补1 int补1/0是看原数字正负,但有些计算机不支持
cout << ~a<<(char)10;
//求反 将所有二进制反,a+(~a)=0b11...1=-1
cout << (0xffff0ff0 & 0xff00fff0)<<endl;
// 与 游戏颜色4bytes 透明+rgb 可用于读某个位置的数,读后可以进行位移
cout <<std::bitset<8>(0b10010011 | 0b00100110) << endl;
//或:与搭配,进行加减
int d = 123, b = 241, c = b ^ d;
//用c来记录a,b的关系,然后再还原 ,异或运算的三角关系 
cout << (c ^ d) << endl << (c ^ b);
//可用于检验其中一个数据被篡改 
//单机游戏检测到作弊后,降低爆率,卡bug来制裁
//对于对立事件,如按大小顺序进行排序

​ leetcode 1684 利用位运算来记录字母是否出现

其他

逻辑运算

&& 将短的条件放在前面

条件运算

三个数比大小

int max = a > b ? (a > c ? a : c) : (b > c ? b : c);

赋值运算

对于二维数组赋值运算,应先赋值连续段

控制语句

判断

多个条件并列时,简单的放在前面,提高效率

if (){}
else if (){}
else{}

//if else就近匹配
//简单判断用 ? :
switch (grade)
{
    case 1 :cout<<("A"); break;
    case 'A':cout << ("B"); break;//ascii 65
    default: cout<<("expired grade");
}

循环

路由器pin码,crackpass爆破

	int pin{ 12345678 };
	int crackpass{};

re:
	if (crackpass != pin) {crackpass++;goto re;}


	for (int crackpass = 0; crackpass != pin; crackpass++);
	string s1 = "hello world";
	for (auto x : s1)cout << x << endl;

	while (crackpass != pin)crackpass++;

	crackpass = -1;
	do{crackpass++;} while (crackpass != pin);//效率更高,while会被优化成这个

	cout << crackpass;
//循环数组 c++11
int arr[5]{ 1,2,3,4,5 };
for (int i : arr) //此时有个强制转换
	cout << i << endl;

递归函数

int fac(int x)
{
	if (x == 1)return x;
	return x * fac(x - 1);
}
//循环的效率更高,但递归逻辑更清晰?我不觉得
//如果不设置退出条件,栈会被堆满崩溃

函数本质上是功能模块化,使代码更清晰,便于维护和使用

学习函数 : 输入输出/作用

函数

三个部分

参数 : 类型 , 默认选项or自己定义

调用约定 : 描述了函数参数是怎么传递和由谁清除堆栈的。

返回值 : 类型 , 注意错误时的返回信息

在编译时会在函数名前加一堆东西来区分

另外还有函数名和函数体

参数

  • 参数
//形参 即用来占位的形式参数   数组参数 参考冒泡排序
void cprint(char str[]){}
//默认实参 可以不输入,按照默认值进行,只能放在末尾
//指针参数 用于操作外部数据,利用指针和引用传参可以减少内存开销
//常量指针参数 只读取,不改变  ...直接用形参也一样
  • 不定参数
//主函数
//不定参数,用于cmd调用程序 最后有一个空指针 
//用空格打断 依次放入数组,注意my.exe也算参数 当文件名有空格时,可以用" "来整体化
int main(unsigned count, char* arc[]){}

//自定义函数
//鸡肋在于需要自己输入参数个数
#include<stdarg.h>
int average(int count,...)
{
	va_list arg;  //char指针 也可以写成char*arg;
	va_start(arg, count);//将参数地址放入arg
	va_arg(arg, int);//以int规则读取参数,读取后指针偏移sizeof(int)
	va_end(arg);//释放指针
}
  • 不定参数不使用count
template<typename T,short count>
T sum(T(&arr)[count])
{
	T sum{};
	for (int i = 0; i < count; i++) sum += arr[i];
	return sum;
}

int main()
{
	int a[4]{ 1,2,3,4 };
	cout << sum(a);
}

调用约定

描述了函数参数是怎么传递和由谁清除堆栈的。它决定以下三个方面:

  1. 函数参数传递的方式(是否采用寄存器传递参数、采用哪个寄存器传递参数、参数压桟的顺序等)
  2. 函数调用结束后的栈指针由谁恢复(被调用的函数恢复还是调用者恢复)
  3. 函数修饰名的产生方法

实例化用下面两个函数

int add ( int a, int b );
调用约定参数传递恢复栈指针函数修饰名注意
__cdecl(C declaration)栈,从右向左入栈调用者_func _add调用参数可变的函数只能采用这种方式(printf)
__stdcall(__pascal) standard栈,从右向左入栈被调用者,常用于WINAPI中常用_func@num(byte) _add@8因为被调用者清空,就只需要被调用者编译出清空的相关代码.节省空间
__fastcall寄存器和栈,从左往右的前两个由ecx和edx传递,其余从右向左由栈传递被调用者@func@num@add@8有寄存器传递,速度会快很多
__thiscall栈(寄存器),从右向左入栈,参数个数不确定时,this指针最后入栈,反之,通过ecx传递,之后一般存入esi中参数个数确定时,调用者清理,反之唯一一个不能明确指明的函数修饰类的成员函数使用的调用约定

返回

  • 数组
//指针是局部变量,函数结束后消失,而new空间会放在heap里,直到释放
int clen(const char* str)
{
	int i = 0;
	for (i; str[i]; i++);
	return ++i;
}

char* cstr(const char* str)
{
	int len = clen(str);
	char *str1= new char[len];
	memcpy(str1, str, len);
	return str1;
}
  • 指针和引用

返回结构体会给每个数进行赋值,内存开销大,可直接返回指针

//指针
return &a;//返回的地址应该是new的,否者在执行完函数后,内存会被清空
//引用
return *a;//返回的表面上应该是实体
  • return与exit

return是语言级别的,它表示了调用堆栈的返回,会隐式调用exit

exit是系统调用级别的,它表示了一个进程的结束

exit(0)表示程序正常, exit(1)和exit(-1)表示程序异常退出,exit(2)表示表示系统找不到指定的文件

函数的工具

函数重载优先级大于函数模板

函数重载

功能类似时, 对不同函数用同一函数名, 不过只有cpp中能使用

通过参数来区分,先参数个数,再参数类型,类型不同再强制转换

引用会出现歧义 add(int &a,int &b); add(int a,int b);二者都是传送实体

强制转换后匹配引用失败,因为转换的只是括号里的临时变量,没有自己固定的内存地址

const int 与 int 也不能区分, 因为调用函数本来就不会改变参数的值

函数模板

  • 解决对同一逻辑的,固定参数数量,不固定参数类型的函数使用

用来生成函数的模板,本质就是无脑替换

传送引用时,传送的实际是内存地址,不会实例化,因为引用和指针在返回时并不是以原形式返回的,需要类型转化,而模板会全部当同一类型来看

函数模板是对函数的补充,所以模板之下,完全按函数的套路来

使用一函数就会生成一个函数,内存占用up

只在编译器层面存在

//模板声明
template<typename T1,typename T2>
T1 sum(T1 a, T2 b)
{
	return (a + b) / 2;
}
sum<int,float>(100,2.5);//转换时的两个人生导师
//当返回值类型不能由前方运算得到时,必需要进行指定,指定是依次来的,所以最好把返回类型放在第一位

//一些特殊方式(其实也就是把函数的初始化推广一下
//默认类型
template<typename T1=double>
//非类型的模板参数,写在前面好赋值
template<int x,int y =100,typename T>
T mul(T z)
{
   return x*y*z; 
}
mul<10>(5);//调用时,未指定的需要指定
//模板与具体混用
T1 sum(T1 a, int b)

  • 解决指针问题

用于处理指针和引用

template<typename type>
type bigger(type a, type b)
{
	return a > b ? a : b;
}
//用例外解决
//例外优先级比模板高
template<>
int* bigger(int* a, int* b)
{
	return *a > *b ? a : b;
}
//用模板解决
int* bigger(int* a, int* b)
{
	return *a > *b ? a : b;
}
  • 解决参数数量不确定, 用 函数模板的重载
template<typename type>
type aver(type a, type b, type c)
{
	return (a + b + c) / 3;
}

template<typename type>
type aver(type a, type b)
{
	return (a + b) / 2;
}

>decltype

c++11后可用

注 : auto会自动删除const和引用属性,直接显示值

  • auto
//拖尾函数,来解决auto自动删除问题,一般不建议,太麻烦了
auto bigger(int &a, int& b)->int&
{
	return a > b ? a : b;
}
//用int& 代替auto也一样
//不用拖尾将无法赋值
int a=10,b=20;
bigger (a, b)=10;
//在c++14后才能用auto来判断返回值的类型
  • decltype

declare type 解决auto自动删除问题

//I.未经历运算时
//1.就是a的类型,不删除const和引用属性
decltype (a) x;
//2.如果是函数,直接是返回值类型,不会执行表达式
decltype(bigger(a,b));

//II.经历运算后
//1.如果有固定内存地址,则得出该类型的引用
int*pa{&a};
decltype(*pa) x=a;
//2.反之,得出结果类型
decltype(a+0.1) x;//double

//可将->int&改成->decltype(a>b?a:b)
//c++14后可直接写成 ,返回的是引用类型,可对其进行修改
decltype(auto) bigger(int&a,int&b){}

  • 应返回结果与实际返回类型不同时
template<typename T1,typename T2>
decltype(auto) bigger(T1 a, T2 b)
{
	return a > b ? a : b;
}	

int a = 100000;
float b = 2;
bigger(a, b);
//此时经过计算后,因为类型转化,应当返回float类型的引用,但实际a为int类型,无法被float引用
//此时此刻,返回的为float类型,相当于做了一次类型转换
  • 编译器角度理解定义和声明

x32下每个进程都有4GB的内存

全局变量在全局区,有着相对固定的地址,局部变量没有

游戏中,会变得放在堆区,不会变的放在全局变量区

函数的本质

函数名的本质就指令开始的指针

int add(int a, int b) 
{ 
    return (a + b); 
}
//函数名是内存地址,下面代码的两个输出是一样的
cout<<add<<" "<<&add
//函数体是编译好的二进制代码
char*str=(char*)add;
for (int i = 0; i < 30; i++)  
    printf("%02X \n", (unsigned char)str[i]);

函数的具体实现清跳转other->assembly

Lambda 表达式

匿名函数

[ ]( )->int{ } 捕获列表 参数类型 返回值类型 函数体

捕获列表,获取作用域内的变量

返回值类型可不写,编译器可以自动判断

参数类型和函数体跟不同函数一样

  • 捕获列表

[&]按引用捕获 [=]按值捕获 [K=5] 引入新的变量

不加变量就会默认捕获所有全局变量

int a = 10,b=0;
auto f = [b, &a](){a+=b;};
f();
  • 用在sort函数中

C/C++联合编程

static与inline

  • static

静态变量

int test()
{
	static int count{};
	cout << count++ << endl;
}//生命周期与全局变量一样,但只有在该函数中才能访问

静态函数

static int test(){}
//具有内部链接属性
  • inline

内联变量

//c++17才能用,具有外部链接属性,可以多次定义,但最好不要这么做,放在头文件中比较好
inline float pi =3.14159;

内联函数

该函数会建议编译器处理成内联代码以提升性能,短小的函数可以内联

但现在编译器比较智能, 会自动优化,inline的可能不内联,没inline的可能内联

建议放在头文件里,编译更快(内联的本质就是去除调用的过程,在汇编层面直接实现相应功能)

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

声明与定义

意义 : 函数过多时,代码更清晰

声明是各种类型的指针

定义是指针指向的内存地址

//声明
int sum(int a,int b);//也可写成int sum(int ,int)跟函数指针同理 
int sum(int a1,int b2);
extern int sum(int ,int);
//定义
int sum(int a, int b)
{
	return a + b;
}
//定义只能有一次,而声明可以有无数次
//声明不需要内存分配,定义需要
  • 对于变量 的声明与定义
extern int all;
int all {1000};

自己的头文件

头文件查找顺序

  1. 系统头文件:包括C++标准库的头文件(例如iostream、vector、string等)和操作系统提供的头文件(例如Windows.h、ImageHlp.h等)。
  2. 第三方库的头文件:如果你在代码中使用了第三方库,那么应该先包含该库的头文件。
  3. 自定义头文件:包括你自己编写的头文件和项目内部的其他头文件。
    编译时,先将.c/.cpp文件编译为.obj文件,再将.obj文件链接为.exe可执行文件。头文件如果不被调用,就不会被编译。

为了避免编译错误,应该遵循声明n次、定义一次的原则。编译时,先将.c/.cpp编译为.obj文件,再链接为.exe,头文件如果不调用就不会编译

头文件.h用来声明需要使用的函数,也可以写定义,但是有时需要嵌套调用,会导致同一个函数被多次定义。不过,可以使用静态函数和内联函数来定义,因为静态函数只在它所在的源文件中有效。

引用头文件的方式是使用#include"emath.h"。

头文件的开头可以使用#pragma once来防止多次定义。也可以使用#ifndef _HEMATH_和#define _HEMATH_的组合来达到相同的效果,但只对单个源文件有效。这样做的目的是为了防止头文件被多次引用,提高编译速度,因为头文件只会被打开一次。

当其他工程需要使用自己的头文件时,可以先拷贝该头文件,然后将其添加到新的项目中。

以上是关于自己的头文件的一些说明和建议。通过正确的头文件使用和包含顺序,可以避免编译错误和依赖问题,提高代码的可读性和可维护性

cpp调用c语言定义的函数

  • 头文件中声明时

extern "C" int sum();

  • 也可批量声明
extern "C"
{
    int sum();
    int aver();
}
  • 也可在源文件中引用
extern "C"
{
    #include "emath.h"
}

c语言无法调用cpp的函数,除非在声明时,用这种格式extern "C" int sum()

  • 当.h中的文件以extern "C"声明后,c语言调用会报错,怎么办呢,用这个方法
#ifdef __cplusplus
extern "C"
#endif
 int sum();
//c语言无法识别这个宏,而cpp能

声明为c风格函数后,函数就无法重载了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值