C++一些小知识点

文章目录

常见符号常量

在这里插入图片描述
在这里插入图片描述
c++常见初始化方式:

type name = value;
type name = {value};
type name{value}

cout显示十进制,十六进制,八进制:

int a = 30;
using namespace std;
cout << dec;//默认十进制
cout << "十进制下:a = " << a << endl;
cout << hex;
cout << "十六进制下: a = " << a << endl;
cout << oct;
cout << "八进制下: a = " << a << endl;
cout << dec;//改回十进制

const限定符:
格式: const type name = value; 声明时必须初始化。
float精度低于double低于long double
强制类型转换: type(value)
整型从小到大:bool,char,signed char,unsigned char,short,unsigned short,int,unsigned int,long unsigned long,long long,unsigned long long

‘s’ 和 "s"的区别:
‘s’ 其实是83的另一种写法。char c1 = ‘s’;
"s"实际是字符s和\0组成的字符串,其中"s"实际表示的是该字符串的地址。
sizeof 用于数组,则返回数组的总字节数。strlen()返回的是字符数组的可见长度(不包括最后的\0)。

	char arr[10] = "sdfs";
	int s1 = sizeof arr;//10
	int s2 = strlen(arr);//4
	cout<< arr<<endl;//"sdfs"
	arr[2] = '\0';
	cout<< arr << endl;//"sd"

cin接受输入时,以空白(空格,tab,换行)确定字符串结束位置。故当你的字符串包含空白时,只会读取空白前。
cin 按行读取:通过回车来确定字符串结束
cin.getline(name,num);name是存储位置,num是要读取的字符数,注意假设num = 20;则可以读取19个,最后一个自动补\0;
cin.get(name,num); 区别:get()不会读取并丢弃换行符,故第二次使用该语句,会直接遇到换行符而直接结束。可以使用cin.get()来处理换行符。就可以继续接受了。

const int size = 20;
char arr[size];
char arr1[size];
//1
cin.getline(arr,size);
cout<< "***** : "<<endl;
cin.getline(arr1,size);
//等价于
cin.getline(arr,size).getline(arr1,size)/*因为cin.get返回值是cin对象,
可以继续调用*/
//2
cin.get(arr,size);
cout << "**** : "<< endl;
//cin.get();//处理换行符
cin.get(arr1,size);/*由于get不会丢弃读取到的换行符,故该语句碰到的第一个字
符就是换行符,故直接退出。*/
//等价于
cin.get(arr,size).get.get(arr,size);

string类的读取;

string s;
//1
cin>>s;
//2
getline(cin,s);

c++新增 原始字符串输入:即输入时不需要转义字符了

cout << R"(name '' \n   nn)"<<endl;//name '' \n   nn

//等价
cout << "name '' \\n   nn" << endl;

//想要输入的原始字符串包括)"时
cout << R"(saf)")" << endl;//会报错
cout << R"12(saf)")12" << endl;//可以在"(之间加符号,来表示左边界,则右边界也要相同,才会结束;

空类的大小是1,为了确保两个不同对象的地址不同,必须如此。
类的实例化是在内存中分配⼀块地址,每个实例在内存中都有独一无二的⼆地址。 同样,空类也会实例化,所以编译器会给空类隐含的添加⼀个字节,这样空类实例化后就有独一无二的地址了。 所以,空类的sizeof为1,而不是0。

class A{};   //1
class B:public virtual A{};//4 包含一个有指向虚基类的指针

class Father1{}; //1
class Father2{};//1
class Child:Father1, Father2{};//1

指针:指针不为空时,对应的bool值为true。

int *a,b;//实际声明的是一个指向int的指针和一个int

一定要在使用*解除指针引用前,给指针一个确定的地址。

int *a;
*a = 20;//虽然在声明时,a有相应的默认值。但是这个默认值是不确定的。故将20存储在了一个不确定的地址上,是不安全的操作。
int a = 10;
int *b = new int;
//两种声明所分配的内存是不一样的,常规变量在栈中分配内存,而new从堆或自由存储区分配内存
int a[10] = {0};
cout << a << endl;//解释为数组a的第一个元素的地址,是一个int大小的地址
cout << &a << endl;//解释为数组a的地址,是10个int大小的地址
cout << a+1 << endl;//解释为数组a的第二个元素的地址,即加上一个int大小
cout << &a + 1 <<endl;//加上10个int大小;

多数情况下,数组名被视为第一个元素的地址,例外::在使用sizeof(arr)时,将返回整个数组的大小,而strlen(arr)返回可见字符大小。
使用new创建数组的好处在于,动态联编,即在运行阶段指定数组大小。

int size = 0;
cout<< "请输入数组大小: " <<endl;
cin >> size;
int *arr = new int[size];
....
delete [] arr;

对于全局变量 int a 是声明加初始化,对于局部变量 int a 只是声明。

array类的使用

//有了vector,为什么还要用array呢?因为vector虽然方便,但是牺牲了效率。对于需要固定大小的数组,用array更高效和安全。
#include<array>
using namespace std;
array<int,10> arr;
array<int,3> ar = {1,2,3};
int a[3] = {1,1,1};
//array和数组都存储在栈中,vector存储在自由存储区或堆中。
//a[-2] = 12;//该语句不会出错,即数组不会边界检查,这是不安全的。对于array和vector用[]虽然也有这种风险,但是一定会出错,而普通数组有时候即使越界了,但可能还是给你返回一个值,不会终止程序,但是可以使用at(),at()函数会捕获非法索引。
//vector和array提供了更好的数据访问机制,即可以使用front()和back()以及at()(at()可以避免a[-1]访问越界的问题)访问方式,使得访问更加安全。而数组只能通过下标访问,在写程序中很容易出现越界的错误.

cout显示bool值设置: cout.setf(ios::boolalpha);

++,–运算符:相比之下,用户自己为自己的类型重载时,前缀++的效率要高,因为后缀++需要保存当前值的副本,再++,再返回该副本。
前缀递增和前缀递减以及解除引用的优先级相同,从右往左依次。后缀运算符优先级高于前缀运算符

int arr[4] = {1,2,3,4};
auto pt = arr;
int a = *++pt; // 现将pt进行++操作,再解除引用,故pt指向索引1,a=2
int b = ++*pt; //先将pt解除引用,*pt = 2;再执行++,故b = 3,pt指向索引1
int c = *pt++;//由于后缀++优先级高,但c = 3(是因为前一步对索引1上元素进行了++操作),pt最终指向索引2;
int d = (*pt)++; //d = 3;
int e = *pt;//e=4

逗号运算符:

//int a = 20,30;//因为逗号运算符的优先级最低,故a=20 //运行会报错
int c = (20,40);//逗号表达式的值是最后面的。故b=40
//逗号表达式计算顺序是从左到右的
int a = 0;
int b =2;
a = 10,b = a+11;//a= 10;b = 21

一般表达式的值是左侧的值,如 a = 2+18 这个表达式的值就是20
关系运算符优先级小于算数运算符

c风格字符串比较:使用strcmp()

char c1[5] = "wwww";
char c2[5] = "wdww";
strcmp(c1,c2);//返回值有-1,0,1三种,若c1<c2则返回-1,依次

string直接可以使用== ,!=等关系运算符,只要运算符的一侧是string类即可如

string s="dsf";
s == "sdwef";

系统时间:可以使用clock()函数获取当前的系统时间。但是该系统时间表示方式不是我们常见的以秒为单位。系统时间/CLOCKS_PER_SEC = 秒数;

#include<ctime>
int main(){
	clock_t now = clock();
	//假设延时10秒
	float T = 10.0;
	clock_t delay = T*CLOCKS_PER-SEC;
	while(clock()-now<delay);
}

类型别名:

#define INT_p int*;
typedef	int* INT_P;

&& || !也可以使用 and or not

int a = 10;
	if (a++ > 9 || ++a > 12)	cout << a << endl;//11
	int b = 20;
	if (b++ > 21 && b++ < 30);	
	cout << b << endl;//21
	//先判断左侧表达式,如结果已定,就不会判断右侧。如||只要左侧为true,则不会去判断右侧

字符函数库cctype 头文件包含 : #include<cctype>
在这里插入图片描述

c++函数不能返回数组,函数在传递数组时,也要传递数组大小,因为在函数头上时,数组名等价于指针,实际传递的是数组的第一个元素的地址。在函数外面,sizeof arrname 是数组的大小。而传入函数内,sizeof name 是指针大小。

int b = 20;
const int* a  = &b;//这句话的意思是不能通过a来修改指向的值,但还是可以通过b来修改
b = 10;
//此时*a = 10;

//二维数组
int arr[3][4] = {{1,1,1,1},{2,2,2,2},{3,3,3,3}};
//处理二维数组的函数原型
int total(int arr[][4],int row_size);

assert()函数—断言

#include "assert.h" 
void assert( int expression );

c++在头文件一般不会定义函数,在头文件一般给出函数原型,#define或者const定义符号常量,类,结构,模板等的声明,不定义函数是因为,在某一个文件中可能包含了两次这个头文件,则会出现定义重复。
包含头文件使用<>,"“的区别。<>会在存储标准头文件的主机文件系统中查找,”"则首先在当前目录或源代码目录查找,没有找到,才会去标准头文件的位置查找。
怎么避免重复包含同一个头文件?

#ifndef  XXXXX
#define XXXXX
.......
#endif

C++的数据存储区分类:堆(自由存储区):动态分配的;栈:系统自动申请和释放的;静态存储区:在程序整个运行期间都存在的;常量存储区:存放常量的;代码区:存放代码序列的。
在这里插入图片描述

存储的持续性:
自动存储持续性:在函数中声明定义的变量(包括形参),在函数调用时初始化,在函数返回时销毁。
静态存储持续性:在程序的整个运行周期内一直存在。
线程存储持续性:C++11标准引入的新的存储连续性,变量的生存周期即线程的运行周期。
动态存储持续性:用new运算符申请的内存将一直存在,直到手动的调用delete回收内存。

链接性:
1.静态持续性,外部链接性: 声明在代码块外,不加static;
2.静态持续性,内部链接性:声明在代码块外,加static;const修饰的也是内部的
3.静态持续性,无链接性:声明在代码内,加static

//file1
int a = 20;//1
static int b = 10;//2
const int d = 121;//2
void func(){
	static int c = 20;//3
}
///
//file2
extern int a;//使用file1的a
//不使用file1的a
int a = 12;//错误,因为file1的a是外部链接性,现在这个也是外部链接性,重复定义。
static a =12;//正确

说明符:
存储说明符

auto    //现在用于自动类型推断
register //原来用于声明寄存器变量,现在是显示的指出变量是自动的
static	//用于作用域是整个文件的声明时,说明链接性是静态、内部链接性,用于局部变量时,说明变量的存储持续性是静态无链接性的
extern	//引用声明,声明引用在其他地方的变量
thread_local	//用于指出变量的持续性和线程的持续性,thread_local之于线程,就是静态变量之于程序
mutable	 //可以修改const修饰的变量

struct Node{
	string name;
	multable int size;
}
const struct Node node = {"wang",12};
node.name = "li";//错误
node.size = 1; 

const,volatile限定符:

const	//	const修饰的常量的链接性是内部的,但要外部链接性的常量,就需要在声明的时候加extern,在初始化的地方也需要加extern
//file1
extern const int a =10;
const int b = 20;
//file2
extern const int a;
const int b = 20;
//a是共享的,b不是共享的。



volatile	/*对于一个变量,在多个语句用到,系统为了优化,会将该值存放到寄存器,
但前提是该值在多次使用是不会被改变。但有时候可能该变量的值会被硬件什么的改变。
故该关键字就可以避免这种优化*/

对函数而言,链接性都是外部的,但也可以在函数前加static ,则链接性是内部的

RTTI 运行阶段类型识别,只用于包含虚函数的类。

//dynamic_cast运算符可以知道“是否可以安全地将对象的地址转成特定类型”

//将派生类对象向上转化是安全地,将类向下转换是不安全的。
Chilren* children;
Parent* b = dynamic_cast<Parent*> children;
//若是安全地,就转换,若不安全,则返回nullptr;

//typeid运算符 可以确定两个对象是否为同种类型。与sizeof类似,接受两种数据结构,1.类名;2.结果为对象的表达式。typeid返回值是一个type_info对象的引用。其中type_info在头文件typeinfo中。
typeid(Children) == typeid(children)  // 返回true
//type_info重载了==和!=,所以可以比较
//若children是nullptr,会引起bad_typeid的异常
//type_info包含一个name()成员。一般是返回类的名称。

//应该避免的是,能用dynamic_cast的时候,就不要用typeid

类型转换运算符:
dynamic_cast – 如前面
const_cast – 可以改变const’或volatile
static_cast --可以进行类的向下转换,向上转换
reinterpret_cast —危险的转换,需要的时候再看吧。

Children* c;
Parent* p;
Parent* p1 = dynamic_cast<Parent*> c;
Children* c1 = const_cast<const Children*> c;
Children* c2 = const_cast<const Children*> p;//错误使用,只能const特征不同,其他必须相同。
Children* c3 = static_cast<Children*> p;//向下转换可以
Parent* p2 = static_cast<Parent*> c;//向上转换可以
//但不能将没有关系的类对象进行转换

initializer_list 类;
包含在头文件 initializer_list中,使用该类的目的是为了能够将一系列值传递给构造函数或其他函数。
注意点:initializer_list的迭代器是const,只能访问,不能修改

#include<initializer_list>
using namespace std;
int sum(initializer_list<int> v) {
	int total = 0;
	for (auto it = v.begin(); it != v.end(); it++) {
		total += *it;
	}
	return total;
}
int main() {
	initializer_list<int> p1 = { 1,3,2,4,6,44 };
	int t1 = sum(p1);
	int t2 = sum({ 1,2,4,5,7,8 });

	
	return 0;
}

当没有使用initializer_list实现构造函数时,可以使用{}来调用构造函数,可以调用对应参数的构造函数,此时{}和()一样。但当类提供了initializer_list实现的构造函数,则{}只能调用该构造函数。

初始化列表:

	int a = { 4 };
	int* p = new int[5]{ 1,2,3,4,5 };
	vector<int> dp { 1,2,3,5,6 };
	//防止缩窄:
	char c = {1.3e23};

声明:
auto —自动类型推断
decltype ----获取表达式类型
decltype(exp)
1.如果 exp 是一个不被括号( )包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,那么 decltype(exp) 的类型就和 exp 一致,这是最普遍最常见的情况。
2.如果 exp 是函数调用,那么 decltype(exp) 的类型就和函数返回值的类型一致。
3.如果 exp 是一个左值,或者被括号( )包围,那么 decltype(exp) 的类型就是 exp 的引用;假设 exp 的类型为 T,那么 decltype(exp) 的类型就是 T&

int x =10;
decltype(x) y;
double y;
decltype(x*y) z;
decltype(&x) p;

在定义模板时,使用的更频繁;

返回值类型后置

int sum(int a,int b);
//等价于
auto sum(int a,int b) ->int;
template<typename T,typename P>
auto sum(T x,P y) -> decltype(T+P);

模板别名

typedef std::vector<int>::iterator vecintit;
//等价
using vecintit = std::vector<int>::iterator

//区别:using可以用于模板的部分具体化
template<class T>
using mapTint = unordered_map<T, int>;
int main() {
	mapTint<string> hash1;
	mapTint<double> hash2;
}

nullptr

智能指针

作用域内枚举

enum W0 = {red,blue,green};
enum class W1{red,blue};
enum class W2{red,green};
//使用
W1::red;
W2::red;
//这样解决了多个枚举有重复的问题,即名称冲突问题

显式转换运算符 explicit
禁止某种类型自动转换

class A{
public:
	A(int);
	explicit A(double);
	operator int() const;
	explicit operator double() const;
};
//省略实现
int main(){
	A a1(1);//可以
	A a2(1.3);//可以
	//上述两种都提供了对应的构造函数,当然可以,上述两种和explicit没什么关系。
	A a3 = 11;//可以
	A a4 = 2.4;//失败
	// 上述两种就和explicit有关系了,加了explicit关键字,这种转换就被禁止了
	/*在C++中, 如果的构造函数只有一个参数时, 那么在编译的时候就会有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类对象.*/
	int b1 = a1;//可以
	double b2 = a2;//失败
	double b3 = double(a2);//可以
	//上述表明,加了该关键字就会禁止隐式装换
}

左值: 表示数据的表达式(变量名,解除引用的指针),程序可以获得其地址。一开始左值可以出现在赋值语句左边,但对const修饰的,就不能赋值,但可以获得其地址。
右值:可以出现在赋值表达式右边,但不能应用地址运算符的值,包括字符常量,x+y等表达式。以及返回值不是引用的函数。
右值引用:
还有下面这种定义:
左值是指表达式结束后依然存在的持久化对象,右值是指表达式结束时就不再存在的临时对象。所有的具名变量或者对象都是左值,而右值不具名。
左值:可以看做是一个可以获取地址的量,它可以用来标识一个对象或函数。右值:可以认为不是左值的就是右值。

int x = 10;
int y = 23;
int && p1 = 13;//原来不能对13取地址,但是现在可以通过&p1等得到13的地址。
int && p2 = x+y;//后续修改x,y也不会影响p2

包装器


template<class T, class U>
T func(T a, U b) {
	static int count = 0;
	count++;
	cout << "count = " << count << " ,  " ;
	cout << "&count =  " << &count << endl;
	return b(a);
}
double Square(double c) {
	return c * c;
}
class A {
private:
	double num;
public:
	A() :num(10) {};
	double operator()(double c) { return c + num; }
};
int main() {
	func(10.0, Square);
	func(11.3, A());
	func(121.2, [](double a)->double {return a; });
	return 0;
}

在这里插入图片描述
分析:虽然每次函数func的第一个参数都是double,第二个参数都是输入是double,输出也是double的函数指针,但是实例化模板并不止一次。所以为了避免这种情况,引出来了包装器。

#include<functional>
using namespace std;

template<class T, class U>
T func(T a, U b) {
	static int count = 0;
	count++;
	cout << "count = " << count << " ,  " ;
	cout << "&count =  " << &count << endl;
	return b(a);
}
template<class T>
T func1(T a, function<T(T)> b) {
	static int count = 0;
	count++;
	cout << "count = " << count << " ,  ";
	cout << "&count =  " << &count << endl;
	return b(a);
}
double Square(double c) {
	return c * c;
}
class A {
private:
	double num;
public:
	A() :num(10) {};
	double operator()(double c) { return c + num; }
};
int main() {
	function<double(double)> f1 = Square;
	typedef function<double(double)> fdd;
	func(10.0, f1);
	func(11.3, fdd(A()));


	func1<double>(11.2, [](double a) {return a; });
	return 0;
}

可变参数模板

void show_list() {}

template<class T>
void shou_list(const T& value) {
	std::cout << value << '\n';
}
template<class T, typename... Args>
void show_list(const T& value, const Args&... args) {
	std::cout << value << ",  ";
	show_list(args...);
}
int main() {
	int n = 10;
	string s = "sdcshk";
	double d = 12.32;
	char c = 'a';
	show_list(n, s, '!', d, c);
	return 0;
}

在这里插入图片描述

!运算符和~运算符的区别

int a = 3;
int b = !a;//0
int c = ~a;//11111100;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值