[C/C++]提升篇-检测知识漏洞增加编程技巧

15 篇文章 4 订阅

目录

一.运算符的优先级

二.数据类型转换

三.switch和if的选择

四.const int*p与int* const p 的区别

五.底层Const和顶层Const的区别

六.不安全函数

 六.cin>> 返回值

七.getline返回值是cin

八.计算机英语加油站

九.goto语句

十.cmd

十一.VS播放音乐

十二.宽度与对其

十三.原地交换字符串

十四.终端

十五.string转char*类型

 十六.静态库的创建

十七.使用一个循环给二维数组赋值

十八.断言

 十九.什么是空指针?

二十.指针与指针相减

二十一.const和指针

二十二.查看变量类型的函数

二十三.C++类中的默认构造函数显示声明

 二十四.输入密码时候,隐藏密码

二十五.解决多文件开发头文件重复包含的方法

二十六.指针数组与数组指针

二十七.void空指针

二十八.函数指针

 二十九.引用类型

三十.指针引用

三十一.内存复制函数

三十二.函数的栈空间(避免栈空间溢出) 

 总结:


对知识的查漏补缺


  • 🎈个人主页:北·海
  •  🎐CSDN新晋作者
  •  🎉欢迎 👍点赞✍评论⭐收藏
  • ✨收录专栏:C/C++
  • 🤝希望作者的文章能对你有所帮助,有不足的地方请在评论区留言指正,大家一起学习交流!🤗

一.运算符的优先级

一共15个级别

  • 最高优先级 : () []
  • 最低优先级 :逗号表达式
  • 倒数第二低优先级 : 赋值和符合赋值(=,==,-=...)
  •  ! >算术运算符 > 关系运算符 > && >> || >赋值运算符

二.数据类型转换

  •  隐式类型转换
    • 算数转换
      • char int long longlong float double
      • 尽量不丢失精度,会将计算结果往精度大的类型转换
      • 例如 : 15 + 3.14    =>   15.0  +3.14
    • 赋值转换
      • int x = 3.14*10.0;
      • 转换为int类型时候,因为赋值符号的左边变量为int,则将类型转换为int类型
      • 结果为 31
    • 输出转换(C语言)
      • printf("%c",255+50); 
      • 305的二进制数是 0000 0001 0011 0001,由于超过了一个字节所能存储的最大数,,所以会将0000 0001挤到第二个字节里面去,该字节就存放0011 0001,十进制为49,即ASCLL码为49,转换为字符为 ' 1 '
      • 若按%d 进行输出,则输出为305,因为int占四个字节,足够容纳305的二进制
  • 强制类型转换
    • c语言 (数据类型)数据   (int)3.14
    • c++语言   数据类型(数据) int(3.14)
    • x  = (char)257 + 100;
    • 257为int类型,其二进制为 1 0000 0001,转换成char类型会将保留8位,结果位0000 0001,这样结果在和int类型的100相加,等于 1+ 100  = 101
  • 高级强制类型转换
  • static_cast
  • dynamic_cast
  • reinterpert_cast

三.switch和if的选择

  • switch : 用于 int char long longlong类型的变量,和多个特定常量的判断处理
    • float和都变了类型不可以
  • if适用于各种逻辑判断
  • switch一定能改为if,if不一定能改为switch

四.const int*p与int* const p 的区别

        

int i  =0;
int t = 2;
const int *p = &i;//可以改变p的指向,但是不能通过*p改变指向变量的值
int* const p1 = &i;//可以通过*p改变指向变量的值,但是不能在指向其他变量了
//*p = 3;//error
//p = &t;//pass
//*p1 =3;//pass
//p1 = &t;error
  • 常量指针可以指向常量或变量,不能将常量指针赋值给普通指针

五.底层Const和顶层Const的区别

  • 底层const用于修饰指针或引用目标对象,表示目标对象是常量,不可修改。
  • 顶层const用于修饰指针或引用本身,表示指针或引用本身是常量,无法指向其他对象。
  • 顶层const,指针本身是常量

六.不安全函数

  • scanf_s函数
int x;
scanf_s("%d",&x);//不需要使用第三个参数,用法和scanf相同

float f;
scanf_s("%f",&f);//不需要使用第三个参数,用法和scanf相同

char c;
scanf_s("%c",&c,sizeof(c));//需要使用第三个参数,否则有警告

char name[16];
scnaf_s("%s",name,sizeof(name));//需要使用第三个参数

int age;
char name[16];
scanf_s("%d%s",&age,name,sizeof(name));
  • gets函数不能使用
    • 使用gets_s,gets函数是老标准C语言函数,vs使用更安全的C11标准,使用对应的gets_s
char line[32];
gets_s(line,sizeof(line));

 六.cin>> 返回值

  • if((cin>>word) == 0){}//在vs里面不能通过编译
    • 解决方法
if(!(cin>>word)){}//方法1

if((bool)(cin>>word) == 0){}//方法2

七.getline返回值是cin

  • 返回值是cin
getline(cin, word) >> count;

if(getline(cin,line)==0){}//错误,不能通过编译
  • 解决方案和上面cin的解决方法一样,可以强制转换类型或者加上逻辑非

八.计算机英语加油站

  • bool 布尔 逻辑类型
  • if         如果
  • else     否则
  • switch  开关
  • case     情况
  • default   默认
  • commit  提交
  • clone     克隆   复制

九.goto语句

  • 例子 :
string ret;
for(int i = 0;i<5;i++){
    cout<<"开始第"<<i+1<<"次相亲..."<<endl;
    cout<<"你喜欢打王者吗?"<<endl;
    cin>>ret;
    if(ret != "yes") continue;
    else{
        cout<<"我中意你,你中意我吗?"<<endl;
        cin>>ret;
        if(ret == "yes"){
            goto happy;
        }
    }
}
happy :
    cout<<"幸福生活"<<endl;
    cout<<"开启幸福之旅"<<endl;
    return 0;
}
  • goto在应用开发中不建议用,会破坏程序的结构性,一般用在底层开发,追求效率
  • 语法 :goto 标志;    标志:
  • goto在应用开发中一般将标志后面的语句封装为函数,进行函数调用 ; 或者用flag进行标记,标记成立执行标志后的代码
  • 标签必须和goto 标志;必须放在同一个函数内,不能跨函数,但是可以在同一个函数的任何位置

十.cmd

  • 切换盘 直接 d:或者c:
  • 进入某个文件,用cd 例如 cd d:\ceshi,就会进入d盘下的ceshi目录
  • 查看编码用chcp(活动码)
  • 修改编码用: chcp  编码  ,例如 chcp  936,会改为936编码
  • 在一个目录下放两个exe文件,利用破解.exe | 客户.exe,会将前面程序的输出数据,通过管道作为后面文件的输入数据
  • 利用dir查看目录下面的文件

十一.VS播放音乐

  • 头文件 
#include<mmsystem.h>
#prama comment(lib,"winmm.lib")
  • 加载音乐代码
mciSendString(_T("play 音乐地址 repeat"),0,0,0);
  • _T可以用多字符集替代
  • play 播放
  • repeat 重复

十二.宽度与对其

  • c++中的设置文本宽度头文件为#include<iomanip>,语句为setw(宽度)
  • 设置对其 : 例如左对齐 std::left

十三.原地交换字符串

  • 当left = right时,没有必要再交换,也可也用于交换其他数据类型
    string str;
    int left,right;

    cout<<"输入一个字符串: ";
    cin>>str;

    left = 0;
    right = str.length()-1;

    while (left<right){
        char tmp = str[left];
        str[left] = str[right];
        str[right] = tmp;
        left++;
        right--;
    }

    cout<<str<<endl;

十四.终端

  • 控制终端大小
  • mode con cols = 列数,lines =行数

  • system("任何终端命令");,再system里面可以写任何终端命令进行执行
  • 例如 : system("mode con cols=40 lines=15");

十五.string转char*类型

  • 函数c_str()函数可以将string类型转换为从const char*类型
using namespace std;
void print(const char * str){
    cout<<str;
}
int main() {
    string str = "LiHua";
    const char* p = str.c_str();//const,char*类型
    print(str);//这样会报错,形参与实参的类型不匹配
    print(str.c_str());//不会报错,将string 转成const char*
}

 十六.静态库的创建

  • 在项目属性c/c++里面,选用无预编译头,创建头文件与cpp文件,需要注意release模式下还是debug模式,在用库时候要与该模式相匹配,库的函数实现是外界无法看到的,最后在要使用的项目里面导入.h文件和.lib文件

十七.使用一个循环给二维数组赋值

  • 行数 : 第几个元素 / 原列数
  • 行数 : 第几个元素 % 原列数
int nums[3][4];
for(int i = 0 ;i<12;i++){

nums[i/4][i%4] = i+1;

}

十八.断言

  • 程序的接口传入进去的数据可能是非法的,要想当传进去的值为非法值时候,给出提示,可以使用断言
  • 对非预期错误使用断言
    • 空指针
    • 输入或输出参数的值不在预期范围内
    • 数组的越界
  • 如果断言的条件返回错误,则终止程序执行
  • 头文件 #include <assret.h>
  • 原型定义
    • void assret(int expression);
  • 语句 assert(数据正常时候的条件)

 十九.什么是空指针?

  • 空指针,就是值为零的指针,(任何程序数据都不会存储在地址为0的内存块中,他是被操作系统预留的内存块,是不能访问的),int *p =NULL;
  • 初始化为空指针,避免访问非法数据
  • 指针不在使用时,可以设置为空指针
  • 表示这个指针还没有具体的指向,使用前进行合法性判断
int *p =NULL;
if(p){

//指针不为空,对指针进行操作
}

二十.指针与指针相减

  • 指针的相加是不行的,被禁止的
  • 指针与指针做减法适用的场合,两个指针都指向同一个数组,相减的结果为两个指针之间的元素数目,而不是两个指针之间相差的字节数
  • 如果两个指针指向不同数组,则相减的结果是相差的字节数,但是这样的相减没有意义
  • 不同类型的指针不允许相减

二十一.const和指针

  • const 写在int之前,则限定不能通过*指针去改变该指针指向的值,但是可以指向别的指针
  • const 写在int之后,则限定可以通过*指针去改变该指针指向的值,但是不能指向别的指针
  • 两个const一个写在int前,一个写在变量名前,限制指针不能指向别的指针,并且不允许修改指针指向的值
  • 总结 : 看const离类型(int)近,还是理变量名近,离谁近,就修饰谁,谁就不能变
#include<iostream>
using namespace std;

int main() {
	int wife = 30;
	int girl = 18;

	//第一种 : 渣男型,普通指针可以随意更改指向与指向地址的值
	int* zha_nan = &wife;
	cout << *zha_nan << endl;

	zha_nan = &girl;
	cout << *zha_nan << endl;

	//第二种 : 直男型,以自我为中心,可以改变指向,但是不可以改变指向的地址的值

	const int* zhi_nan = &wife;
	//*zhi_nan = 25;//报错,不能改变值
	zhi_nan = &girl;
	cout << *zhi_nan << endl;

	//第三种 : 暖男型,专一,不可以改变指向,但是可以改变指向的地址的值

	int* const nuan_nan = &wife;
	//nuan_nan = &girl;//报错,不能改变指向
	*nuan_nan = 25;
	cout << *nuan_nan << endl;

	//第四种 : 超级暖男型,超级专一,不能改变指向,也不能改变指向地址的值

	const int* const _super_nuan_nan = &wife;
	//*_super_nuan_nan = 25;//报错,不能改变指向地址的值
	//super_nuan_nan = &girl;//报错,不能改变指向

	//总结 : const理谁近就修饰谁,理(int)近,则修饰该指针的值不能改变,修饰变量,
	//	    则该指针不能在指向别的变量了
}

二十二.查看变量类型的函数

  • 头文件 #include <typeinfo>
  • 语法 : typeif(变量名).name()
#include <iostream>
#include <typeinfo>

using namespace std;

int main() {

	int i = 10;

	int* k = &i;

	cout << typeid(k).name()<< endl;
}

二十三.C++类中的默认构造函数显示声明

  • 显示声明方式 : 类名 () = default;
  • 作用,如果没有显示声明,类中如果没有其他构造函数的话,系统会默认声明默认构造函数,如果显示声明了,系统则不会声明默认构造函数,但是当加上该语法之后,即使类中有其他构造函数,系统仍然会声明一个默认的构造函数
  • 需要注意的是,salary_data() 的定义应该在类的声明中进行,并且必须是构造函数的声明之一。在类声明中可使用 = default,但在类的定义(实现)中则不能使用 = default。在类定义中,如果没有显式提供构造函数的实现,编译器会自动生成默认构造函数的实现。

 二十四.输入密码时候,隐藏密码

  • 用函数getch(),头文件#include<conio.h>
  • 输入一个字符时候不会回显,getc会回显
  • 实现思路: 输入一个字符,由于不知道密码长度,所以设置为死循环,如果不是回车键,即将该字符添加到存放密码的数组里,顺便打印一个星号,如果输入的为回车键,由于getch()函数读入回车键时候,返回的是\r,所以如果是\r,则将字符数组的末尾加上\0,再跳出循环

//密码输入
void Input_pwd(char* pwd,int size) {
	char c = 0;
	int i = 0;
	while (1) {

		c = getch();//不会回显

		if (c == '\r') {//getch函数读到回车符号为\r
			pwd[i] = '\0';
			break;
		}
		else {
			cout << '*';
			pwd[i++] = c;
		}
	}
	cout << endl;
}

二十五.解决多文件开发头文件重复包含的方法

  • 由于使用多文件开发,需要再cpp文件里面包含.h文件,再将多个.h文件相互包含,可能会出现重复包含的情况,
  • 解决方法 : VS里面特定的方式是,在.h文件的开头加上 #pragma once
  • 公用方法 :在.h文件里面用宏定义,
    #例如在cs.h文件内,宏名一般用该格式,没要求
    #ifndef _CS_H_
    #define
    //代码块
    
    #endif//对应的是ifndefine

二十六.指针数组与数组指针

  • 数组指针 例如 int (*p)[3];
  • 数组指针访问方法 ( p  = &A[0] )
    • 数组法 : (*p)[j];
    • 指针法 : *((*p)+j)     *( *(p + i) + j)
  • 指针数组 例如 int *p[3];.每个元素都是一个变量的地址

二十七.void空指针

  • 其他类型可以自动转换为void类型,但是void类型要转换成其他类型需要强制类型转换
  • 空指针不允许进行算术运算,因为大小不知道
  • 空指针里面仍然存储的是变量的地址,只是类型不知道
	int a = 10;
	int *p1 = &a;
	void* p = &a;
	//cout << *p << endl; error 空指针

	//p1 = p;//error
	p1 = (int *)p;//需要进行强制类型转换

	p = p1;//可以,其他类型可以转换为空指针

二十八.函数指针

  • 函数指针的定义 : 把函数声明移过来,把函数名改为(* 函数指针名)

例如

//函数
int compare_int (const int *,const int *);

//该函数的指针 

int (*fp) (const int *,const int *);

//给该指针赋值

fp = &compare_int;

//传参数两种方式

(*fp)(&x,&y);//第一种,按普通指针解引的方式进行调用
fp(&x,&y);//第二种,直接调用

//(*fp)等同于compare_int
  • qsort()函数
    • 语法 qsort(数组名,数组元素个数,每个元素占的字节,cmp函数);
    • qsort参数为void类型指针,可以比较任何数据类型
    • cmp函数格式
int cmp(const void *a, const void *b) {
	return *(int*)a - *(int*)b ;//由小到大排序
    //换成b-a之后是从大到小排列
}

  • 测试 :
int cmp(const void *a, const void *b) {
	return *(int*)b - *(int*)a ;
}
int main() {

	int q[2] = { 1,2 };
	qsort(q, sizeof(q) / sizeof(int), sizeof(int), &cmp);

		cout << q[0] << " " << q[1] << endl;

}
  • 字符比较,混杂大小写,在比较时候不考虑大小写的方法

 二十九.引用类型

  • 引用变量本身也会占内存,内存的大小等于一个指针变量的大小
  • 对同一内存空间可以取好几个别名
  • 引用可以定义多个,引用也有自己的空间,引用变量像常指针,编辑器在编译时会将引用改为指针
  • 在c++的底层引用就是用指针实现的 Type& name  <----->  Type* const name
  • 在使用的角度,引用会让人误会是一个别名,没有自己的存储空间,这是c++为了实用性而做出的细节隐藏
#include <iostream>
#include <stdio.h>

using namespace std;
int swap1(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
	return 0;
}

int swap2(int* const a, int* const b) {
	int temp = *a;
	*a = *b;
	*b = temp;
	return 0;
}
int main() {
	int x = 10;
	int y = 100;
	swap1(x, y);
	cout << "x = " << x << " y = " << y << endl;

	swap2(&x, &y);
	cout << "x = " << x << " y = " << y << endl;
}

//编辑器会将引用类型的swap1在编译时候转换成swap2的形式

三十.指针引用

  • 可以代替二级指针

int home1(int ** meipo) {
	static int boy = 18;
	*meipo = &boy;
	return 0;
}

int home2(int* & meipo) {
	static int boy = 18;
	meipo = &boy;
	return 0;
}
int main() {

	int* meipo = nullptr;
	home1(&meipo);
	cout << "meipo = " << *meipo << endl;

	//指针引用,可以代替二级指针,
	home2(meipo);
	cout << "meipo = " << *meipo << endl;
	return 0;
}

三十一.内存复制函数

  • 将一段内存里面的数据复制到另一段内存种
memcpy(&b,&a,sizeof(a));
//将a变量内存,复制给b内存一份,大小为a变量的大小

三十二.函数的栈空间(避免栈空间溢出) 

  • 错误1 当数组的内存占用较大时,会引发异常
#include <iostream>
using namespace std;
int main() {

    char buff[2000000];
    cout << (int)buff[sizeof(buff) - 1] << endl;

 

  •  错误 2
    • 当调用次数较小的时候,栈内存还没有满,可以输出,该地址相减除以1024等于100,就是每调用一次函数,分配的内存数,

  • 当调用函数次数较大时候,栈内存爆掉了,就会引发异常
  • 当n=20时候

  •  内联函数inline:
    • 可以解决该函数调用时间上的问题,可以缩短调用时间
    • 但是会将主调函数编译后的代码块变得十分 "臃肿"
    • inline定义的函数,在编译时,会将该函数的函数体直接插入到调用处
    • 就相当于内联函数的函数体在调用出重新写了一遍

 总结:

  • 栈的内存默认为2兆,内存可以修改
  • 站内存储的是非new或molloc出来的变量,没调用一次函数,就会在栈内开辟一段空间,大小为该函数内所有变量的内存和,当内存过大时,栈爆掉就会引发异常
  • 程序是从高内存向低内存进行存储的,两个相邻空间的内存地址相减就能得到
  • 每分配一块空间,这块内存空间就叫做 " 栈帧 "

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北·海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值