本文是自己学习C++的笔记,适用与有C语言基础的同学一起学习,中间一些和C语言一致的部分大多没有写
目录
文章目录
第1章 基本概念
1 using的两种用法
1 直接包含整个命名空间
这种方法相当于把整个std中的名称全部包含进来了
#include <iostream>
using namespace std;
int main()
{
cout<<"hello world"<<endl;
}
- 优点
- 可以直接用std中的名称
- std所有的都可以直接用
- 缺点
- 可能会容易出现命名相同的情况
2 只包含命名空间中的某个名称
#include <iostream>
using std::cout;
int main()
{
cout<<"hello world"<<endl;
}
相对最好
3 直接不包含命名空间
繁琐
#include <iostream>
int main()
{
std::cout<<"hello world"<<endl;
}
2 转义字符
\n //换行
\t //水平制表符
\v //垂直制表符
\b //退格
\r //回车
\f //换页
\a //警告字符
\ //转义字符
\n和endl的区别没搞清楚
3 幻数
在C++编程中,幻数通常指的是程序中直接使用的常数。这些常数可能是数字、字符串或其他类型的字面量,它们在代码中直接出现,没有明确的命名或定义。使用幻数会使代码的可读性和可维护性降低,因为当需要修改这些值时,必须手动在代码中搜索并替换它们。此外,幻数的存在也增加了代码的耦合性,使得代码更难以重用和测试。
为了避免幻数带来的问题,程序员通常会采取一些策略来管理这些常数。例如,可以使用宏定义(#define)或枚举(enum)来替代幻数。这样,当需要修改这些值时,只需要修改宏定义或枚举的值,而不需要在代码的多个地方进行搜索和替换。同时,这也提高了代码的可读性和可维护性。
ps: cout中<<的作用**
流运算符,可以把它理解为数据流向。
"<<"输出流运算符: 作用是把运算符右边的数据输出到运算符左边的目标。
比如cout << 3.14,就是把数字3.14输出到控制台。
">>"流输入运算符: 类比"<<"
float num;
cin >> num;
流运算符可以连续使用,
比如cout << 3.14 << “ ” << “hello world!”,控制台就会显示
3.14 hello world!
反过来也是。
float numA;
int numB;
cin >> numA >> numB;
就会依次读入一个float和一个int,这两个在输入时用空格分隔。
vscode cin的问题
找到设置选项,输入run in terminal ,勾选Whether to run code in Integrated Terminal.
第2章 基本数据类型和计算
##1 数字的前缀后缀(进制和符号)
- 16进制 前缀(0x): 0xFFFFF
- 8进制 前缀(0(零不是O)): 0123
- 无符号 后缀u/U
- long 后缀L或l ,(不推荐用小写L,容易搞混)
- 对于浮点数
- float 后缀F或f,
- double 默认不用加
- long double 后缀为L
##2 算数优先级
* / % 第一优先
+ - 第二优先
//常用的+-*/%不说了
// | 按位或
// ~ 按位非
// & 按位与
// ^ 按位异或
3 设置字符占位长度
cout << setw(10) << 10 + 20 << endl;
cout << setw(10) << 10 * 20 << endl;
setw(10)将下一个输出的字段宽度设置为括号中的字符数
4 定义变量可以用","隔开后分行
int apples = 10, //可以这么加注释
children = 3;
5 多次赋值
a = b = 100; //将100先赋值给b,再赋值给a
6 a++与++a
int a = 0;
cout <<"a=" << a << endl;
cout << "a++=" << a++ << endl; //先赋值再加一
cout << "a=" << a << endl;
cout << "++a=" << ++a << endl; //先加一再赋值
cout << "a=" << a << endl;
输出
=0
a++=0
a=1
++a=2
a=2
7 const常量
const int a = 1;
int main()
{
a++; //不小心修改了常量,此处会报错
return 0;
}
8 关于float/double/long double 的精度问题
代码:(fixed的用法也在此处)
#include <iostream>
#include <iomanip>
//这个代码用于测试float精度的概念
using namespace std;
int main()
{
float x = 0.123456789f; //后面加f表示float
double y = 0.123456789123456789; //不加默认为double
long double z = 0.123456789123456789123456789l; //加L表示long double
cout << setprecision(30) <<fixed; //修改显示格式
cout <<setw(30) <<"float保留的位数"<< x <<endl;
cout <<setw(30)<<"double保留的位数"<< y <<endl;
cout <<setw(30)<<"long double保留的位数"<< z <<endl;
cout <<setw(30)<<"long double保留的位数"<< z <<endl;
return x;
}
output:
float保留的位数0.1234567 9104328155517578125
double保留的位数0.12345678912345678 3796584090851
long double保留的位数0.12345678912345678912 2727263186
后面加f表示float
不加默认为double
加L表示long double
**这个精度是一个粗略值,如果出现一个相差108的两个数相加,较大的数是没有变化的 **
float a = 12345678.0; float b = 0.1; cout << setw(30) << a +b << endl; output:12345678
9 拓展字符集wchar_t
wchat_t(wide characters:宽字符),PC往往是2字节,底层是unsigned short,部分编译器也可以是4字节,特别是Unix工作站.
wchar_t wide_letter = L'z';
wchar_t wide_letter = L'\x0438';
wcin>>wide_letter;
wcout<<wide_letter; //值得注意的是,并不一定会输出,因为可能操作系统并不会识别
ps:练习题不通过if等语句判断两个数的大小;
//输出较大的那个
//解法1
c = ( a + b ) / 2 + abs( a - b );
//解法2
int num[2]={ a , b };
c = num[ a < b ];
第3章 处理基本数据类型
1 数据类型转换
如果两个操作数不同类型,就把取值范围比较窄的那个转换为另一个值较宽的类型.
应该尽可能避免编写混合表达式
应该避免出现宽类型赋值给窄类型,如果故意为之(这并不是常见现象,若出现很多,因考虑是否应该选择更合适的类型),应该使用 显式强制转换 .
将float变量赋值给int时,会丢失小数部分**(全部舍弃而不是四舍五入)**
将float赋值给double,也将丢失精度.
运算时应该**统一选用类型(是其中的一个因素)**请看下面的例子
//假定a为一个不超过255的数;
//方式1:
unsigned char a;
unsigned int b,c;
c = a + b;
//方式2
unsigned int a,b,c;
c = a + b;
上述例子中虽然a的值不超过255,但是由于a与一个unsigned int数据进行计算,虽然节约了空间(不一定),中间存在多次隐式格式转换,运算会慢.
2 显式强制转换
即强制转换
int a = 100000;
char b = 0;
b = (char)(a); //c语言中的老式的转换方法,包含了新式的4种方法
//仍然可用,但是不够清晰,可能得不到想要的结果
//新式的
b = static_cast<char>(a);
3 sizeof()
sizeof()是一个操作符,而不是某个函数
int main()
{
sizeof(1);
return 0;
}
这已经是一个完成的程序了.虽然并没有输出,但是也没有报错,sizeof()应该说是一个长得奇怪的加减乘除.
需要注意的是,sizeof()的返回值是size_t类型(其实就是uint_t),如果用size_t类型则需要#include<iostream>
4 枚举数据类型
枚举类型的定义
int i;
const int j= 5;
enum example{FIRST_DAY = 1};
enum Weekday{Monday = 1, //默认为0,但是可以直接初始化为其他值
Mon=1, //枚举成员不一定有唯一值
Tuesday=5, //中间成员也可以自己随意定义
Wednesday, //默认为上一行的值+1
Thursday=5, //重新从5开始递增
Friday, //默认为上一行的值+1
Saturdary,
//Sat = i, //会报错,赋值必须为常量
//Sat = j, //常量
//Sat = FIRST_DAY,//常量
//Sat = j+FIRST_DAY,//常量
Saturday};
匿名枚举:不给枚举类型起名字,直接定义好几个变量,之后无法再定义这个类型的枚举变量
enum {First = 1,Second = 2}me,you,he;
枚举变量的定义
enum Weekday{Mon,
Tues=5} yesterday=Tues; //这是一种声明方法,
//不给初始值则为随机数,可能超过该枚举类型中包括的范围,
Weekday today = Tues; //这是另一种,枚举变量只能从Weekday的值中选
Weekday tomorrow = static_cast<Weekday>(7);
//显式强制转换的时候可以取min(Weekday)到max(Weekday)中间的任意值
整型不会自动转换为枚举,但是枚举可以自动转换为整型
5 typedef的用法&用途
typedef shdfnowaoiefh int;
shdfnowaoiefh i=0;
用途:主要用于例如:
不同的操作系统或芯片int表示的位数不同,那可以给int起一个别名,
这样移植代码的时候只需要把int换成 long 或short即可.
6 static的两种用法
- 定义静态变量
- 定义局部函数
7 volatile的用法
编译器重复使用某一变量的时候,可能会优化使其从之前加载过的寄存器中读取,但是此时其内存中的数据可能已经改变了,这就造成不可预料的结果.volatile修饰的变量每次都会从内存中读取.
8 extern 外部变量
略
第4&5章 判断循环
1 条件运算符
d=c? a:b; //若c为真,则d=a,反之,d=b
//甚至可以三个选项组合
cout<<(a<b? "a小于b":(a==b? "a等于b":"a大于b"));
2 continue&&break
continue:跳过这次循环
break:跳出循环
第6章数组字符串
1 数组的定义
- 数组长度必须为定值
- 数组长度尽量不要设置为幻数
- 用变量代替数组长度的时候要加const
多维数组定义时,只能省略行数,其他均不可省略,即只能省略第一维.
2 string的使用
部分见头文件string
第7章 指针
指针常量和指向常量的指针
指针常量:指针是个常量,但是指向的地址存储的不一定是常量
const int * point;
指向常量的指针:
int* const point;
指向常量的指针常量:
const int* const point;
动态内存分配
自由存储区
new&delete
//变量
double * pvalue = 0;
//第一种分配方法
pvalue = new double;
*pvalue = 99.0;
//第二种分配方法
pvalue = new double (999.0)
/*******************************/
//数组
pstring = new char[20];
delete pvalue; //释放内存
pvalue = 0; //指针归位
//数组
delete [] pstring; //这个括号很重要.
pstring = 0;
一定记得不用的内存要删除掉!!!
第8章 使用函数编程
1参数与变元
参数:函数定义的时候的形参
变元:函数使用的时候的实际值
特点\分类 | 普通参数 | 指针参数 | 引用参数 |
---|---|---|---|
样式 | int function(int a) |
int function(int * a) |
int function (int & a) |
特点 | 函数对变元的副本进行操作 | 函数对地址a所在的数值进行操作 | 函数对a的本体进行操作 |
原本的值不变 | 可以改变* a | 改变的是原来的变量,且非常不明显 | |
不推荐使用 |
如果确定不需要对传送的参数进行修改,就可以直接将参数定为常量.
int larger(const int & m, const int & n);
2关于函数形参默认值
直接对参数赋值就行了,但是有默认值的要放在最后定义,因为有默认值的参数可以省略处理,假如放在中间容易造成混淆
函数本体不需要写入默认值,声明出赋值就好了.
3 返回值
返回地址的时候不要返回局部变量的地址
不要从函数中返回自动局部变量的地址
不要从函数中返回自动局部变量的地址
可以new一个新变量返回,缺点是很容易内存泄露.用完一定要释放内存
内联函数
inline int larger(int m, int n)
{
return m>n?;
}
前面加上inline,便是内联函数
作用:防止函数过短,跳转花费时间很长,建议编译器把这行代码直接插入到原代码中.
建议的意思是编译器可能不采纳,如果不采纳,可能效率反而降低
把函数声明位inline,那么它就必须在调函数的每个源文件使用,所以必须放在头文件中.
第9章函数
1函数的重载
通过例子理解:对于取较大值的函数larger(a,b). 我们很可能既要用于整型,又要用于浮点,若还想用同一个名字,那么需要用到用到函数的重载
要求:(or)
- 参数的个数不同
- 参数的类型至少有一个不同
效果:
- 编译器会自动选择合适的函数
什么叫合适啊?
如果有完全一致的,无脑选
如果有可以无丢失自动转换的也行(有long的函数,可以(不一定)直接处理int的,double 可以处理float)
int即可以无损到double,又可以到long,则会报错,编译器不抛硬币.
解决办法就是把其中一个参数做一个强制类型转换,编译器就可以处理了,但是最好还是两个都做,这样给人更好的阅读体验.
````cpp
long larger(long a,long b);
double larger(double a,double b);
int main ()
{
int a_int,b_int;
cout << "Enter two integers: ";
cin >> a_int >> b_int;
cout << "The larger of " <<static<long>(a_int) << " and " << b_int << " is "
<< larger(static<long>(a_int),static<long>(b_int)) << endl;
return 0;
}
double larger(double a,double b)
{cout << "(this is double)"<< endl;
return a>b?a:b;}
long& larger(long& a,long& b)
{cout <<"(this is long)"<< endl;
return a>b?a:b;}
output:this is double;
````
上述代码会使用double,两个larger函数中,long的那个可以对参数的**本体**进行修改,所以传入的必定为本体,而不是[复制出来的副本](#1参数与变元),所以强制类型转换出来的临时变量并没有被使用.要解决这个问题,可以将声明函数改为`long larger(const long & a, const long & b);`
注意只返回了long 如果返回引用,也必须加上const
2 函数模板
template <class xxx>xxx larger(xxx a,xxx,b)
{
return a>b? a:b;
}
template:表示是一个模板
<class xxx>:尖括号包裹住参数列表,class xxx表示xxx为参数名(相当于xxx表示某种类型,有时候class也会用typename代替)
#include <iostream>
using namespace std;
template <class xxx> xxx larger(xxx a, xxx b);
int main()
{ long a_long,b_long;
double a_double,b_double;
float a_float,b_float;
int a_int,b_int;
//input the nums
cout << "Enter two integers: ";
cin >> a_int >> b_int;
cout << "Enter two floats: ";
cin >> a_float >> b_float;
cout << "Enter two doubles: ";
cin >> a_double >> b_double;
cout << "Enter two longs:";
cin >> a_long >> b_long;
//complete
cout << "long: " << larger(a_long,b_long) << endl;
cout << "double:" << larger(a_double,b_double) << endl;
cout << "float:" << larger(a_float,b_float) << endl;
cout << "int: " << larger(a_int,b_int) << endl;
return 0;}
template <class xxx> xxx larger(xxx a, xxx b)
{ return a>b ? a:b;}
3 显式指定模板参数
- 编译器有时候不能确定该用哪种参数创建函数
- 编译器不知道该用哪个版本的函数
- 避免太多的函数版本,可以强迫编译器调用某种模板
例:large<long>(a_int,b_int)
:指定xxx为long
4定义模板说明
#include <iostream>
using namespace std;
//声明
template <class TReturn, class TArg> TReturn larger(TArg a, TArg b);
template<>long* larger(long* a, long* b);
int main()
{
long a_long,b_long;
cout << "Enter two longs:";
cin >> a_long >> b_long;
cout << "long: The larger of " << a_long << " and " << b_long << " is " << larger<int>(a_long,b_long) << endl;
cout << "int: The larger of " << a_long << " and " << b_long << " is " << * larger<long*>(&a_long,&b_long) << endl;//注意此处*的使用
return 0;
}
//模板
template <class TReturn, class TArg> TReturn larger(TArg a, TArg b)
{
cout<<"standard version"<< endl;
return a>b ? a:b;
}
//模板说明:特殊情况下的怎么调用
template<>long* larger(long* a, long* b)
{
cout<<"specialized version"<< endl;
return *a>*b ? a:b;
}
5 非类型的模板函数
#include <iostream>
using namespace std;
template <int lower,int upper,class TArg>bool isin_range(const TArg);
int main()
{
double a = 5;
cout << isin_range<0,1>(a)<<endl;
return 0;
}
template <int lower,int upper,class TArg>bool isin_range(const TArg a)
{
return (( a > lower )&&(a < upper))?true:false;
}
6 函数指针
long fun1();
//声明一个函数指针
long (*pfun)(long*,int) = fun1; //一个指向函数的指针声明
//返回类型(*指针名)(参数类型列表)
//声明指针时应该总是初始化它
汇总 常用头文件
iostream
1. cin: 输入
cin默认只读到第一个空白字符,因为提取运算符把空格当作输入之间的分隔符,甚至不能使用提取运算符一次读取一个输入字符,因为任何空白字符包括’\n’都会看作分隔符.
如果想要读到一整句话,则需要
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
char a1[20];
cin.getline(a1, 20);
cout << a1 << endl;
return 0;
}
cin.getline(text,maxlength,'!');
将输入的最多maxlength个字符存入text中,以’!'结束.
其中第三个参数可缺省,缺省时以`\0`为结束
2. cout: 输出
3. fixed & scientific
fixed :固定采用小数点
scientific:固定采用科学计数法
[fixed的用法,scientific类似](# 8关于float/double/long double的精度问题)
4. hex&dec&oct
使用方法类似与fixed,分别表示16进制和10进制以及8进制
5 表
iomanip
io代表输入输出,manip是manipulator(操纵器)的缩写
它是I/O流控制头文件,就像C里面的格式化输出一样。
以下是一些常见的控制函数的:
1.setw(int n)
预设输出宽度
2.setfill(char c)
使用c作为填充字符
cout<<setfill('*');
cout << setw(9) << red << endl;
output:
// ***ff0000
3.setbase(int n)
预设整数输出进制
4.setprecision(int n)
用于控制输出流浮点数的精度(四舍五入)
cstdlib
应用于整数的数字函数
1. abs()
绝对值
2. div()
除法
#include <iostream>
#include <cstdlib>
using namespace std;
int main ()
{
int value =93;
int divisor = 17;
div_t results;
results = div(value , divisor);
cout <<results.quot <<" "<< results.rem << endl;
}//quot=value / divisor rem = value % divisor
quot是商,rem是余数
3. rand()
随机数:从 0 到RAND_MAX的伪随机数(固定的随机数)
获得从0- N(自定义)的随机数(不包含N)
const int N= 10;
int random_value = static_cast<int>(
( N*static_cast<long>(rand()) )
/(RAND_MAX+1L) ) //注意此处的强制类型转换
4. srand()&time(0)
srand(),括号内为种子数,一般与time(0)一起用.
int main ()
{
cout <<"默认的随机数:输出4个从0 到"<< RAND_MAX << "随机数"<< rand()<< " " << rand()<< " " << rand()<< " " << rand() << endl;
srand((unsigned int)time(0));
cout <<"给了一个新的种子数后:输出4个从0 到"<< RAND_MAX << "随机数"<< rand()<< " " << rand()<< " " << rand()<< " " << rand() << endl;
}
output :
默认的随机数:输出4个从0 到32767随机数41 18467 6334 26500
给了一个新的种子数后:输出4个从0 到32767随机数27566 5718 31705 5451
cmath
这些函数的变元可以是任意浮点类型,返回结果与变元类型相同.
参数为float 输出为float
参数为double 输出为double
参数为int 输出为double
1. sqrt()
开方
2表
limits
包含所有标准数据类型的上下限信息.
1.numeric_limits<类型名>
numeric_limits<int>::max();
numeric_limits<int>::min();
numeric_limits<int>::digits(); //二级制数字的位数,不包括符号位,此处输出为31
numeric_limits<unsigned int>::digits(); //二级制数字的位数,此处输出为32
string
部分见2 string的使用
1 string 类定义
using namespace std;
string s1; //长度为0
string s2 = "it just a example";
string s3("it just a example");
string s4(5,'z'); // s4 = "zzzzz"
cout << s1.length() << endl;//显示长度
s1 = s2 + s3; //
2 getline()
const int cA1_SIZE = 20;
string text;
char a1[cA1_SIZE];
getline(cin,text); //从第一个参数,输入到第二个,不考虑长度,第三个参数为停止字符,可省略
cin.getline(a1,cA1_SIZE);
[注意区分getline 和 cin.getline](#1. cin: 输入)
3 substr()
获取一个子字符串.
string a1 = "1224748556556";
string b1 = a1.substr(4,6); // 第一个参数是索引地址,第二个是读取个数
4 compare()
string a,b;
a.compare(b);
a.compare(2,4,b,3,4); //用a从索引2,长度4.与b的索引2,长度4比较
比较a和b,如果a大于b就返回正,等于返回0,小于返回1
不是比较长度,而是比较从索引按顺序,谁的先大.
5 find()
string a ="123422343234";
string b = "23";
a.find('x');
a.find(b); //返回相同的第一个字符的索引
a.find(b,3) //带搜索起点
找到返回索引地址,找不到返回string::npos
即非法地址.编译器不同,数值不同
6 find_first_of()
int main()
{
string a ="0 123,08 41~!@#$%^&*(64123 \tad1\n23 412409";
int i = 0;
int number =0;
for(i = 0;i < a.length(); i++)
{ //检索这些符号
if(a.find_first_of(" \n\t,.?!",i) != string::npos)
{
i = a.find('1',i);
number++;
}
else break;
}
cout <<"表示间隔的符号出现了 "<< number <<" 次" <<endl;
return 0;
}
7 insert()&replace()
a.insert(13,b,5)
将b的前5个字符插入到a的索引为13的字符之前.
a.insert(13,b,5,6)
将b的索引为5开始的前6个字符插入到a的索引为13的字符之前.
a.insert(13,7,'*')
将7个’*'插入到a的索引为13的字符之前.