文章目录
变量和基本类型
一、变量
- 无符号和有符号加减会把有符号转化为无符号
- 对于有无符号只是解释方式不同,运算法则还是补码
区别在于把最高位是否视为符号位(示例):- 有符号
( 10001 ) 2 = − 15 (10001)_2=-15 (10001)2=−15 - 无符号
( 10001 ) 2 = 17 (10001)_2=17 (10001)2=17
- 有符号
二、字面值常量
1.进制
- 0开头八进制,所以类似于08这种会报错
- 0x | 0X开头16进制
2.字面值类型
- 算术类型,指针和引用——是
- 自定义类型,IO库,string——否
三、声明和定义
1.声明
extern int i;
2.定义
int i;
3.具体用法
四、引用和指针
1.引用
引用并非对象,定义引用就无法绑定到另外的对象,使用就是访问最初绑定的对象
2.指针
指针可以改变
五、const
1.const引用类型的转化
初始化常量引用允许任意表达式作为初始值,只要该表达式结果能转换成引用类型即可
//原理
double val = 3.14;
const int& ri = val;
|
V
const int temp = 3.14;
const int& ri = temp;
2.const指针
- const int *p——指向常量的指针,底层——作用指向的对象
- int* const p——常量指针,顶层——作用对象本身
六、处理类型
typedef,using
七、decltype
1.注意点
int i=42,&r=i,*p=&i;
decltype(r)=int&
decltype(r+0)=int
decltype(*p)=int&----解引用指针可以得到指针所指的对象的引用
decltype((i))=int&
当我们使用decltype作用于某个函数时,它返回函数类型而非指针类型,因此我们需要显示的加上*已表明我们需要返回指针
2.与auto的区别,以及和typeid的区别
如果使用引用类型,auto会识别为其所指对象的类型,decltype则会识别为引用的类型。
//有兴趣可以用编译器看一下类型
int x=10;
int &r=x;
auto b=r;
decltype(r) c=x;
typeid主要用于运行时类型识别,auto和decltype则是在编译时识别的,作用时期不同。
八、预处理器
字符串,向量和数组
一、string,vector,迭代器
1.string
- getline会忽略回车符
- size_type和有符号数混用会造成歧义,因为size_type是无符号的
- +两侧运算对象至少一个为string
2.vector
- ()构造,{}初始化列表
- 使用花括号但是提供的值不能用来列表初始化,就要考虑用这样的值构造vector对象
3.迭代器
迭代器不允许相加
二、数组与指针
原则:从内到外,从右到左
int *a[10]; //a数组含有10个指针整型
int &a[10]; //错误,不存在引用的数组
int (*a)[10]; //a是一个指针,指向一个含有10个整数的数组
int (&a)[10]; //a是一个引用,引用一个含有10个整数的数组
int *(&a)[10]; // a是数组的引用,数组含有十个指针
- 内置下标运算符所用的索引值是有符号类型
- 要使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型
表达式和语句
一、表达式
- 左右值,左值表达的是对象本身,右值则是对象的值,举个最简单的例子,int x=5;x就是左值,而常量5就是右值
- 逗号运算符——返回的结果是右侧表达式的值
int y = 3;
int x = (y + 4, 5 + 6, 4 + 5);
cout << x << endl;//x=9
二、语句
- 范围for循环,因为预存了end()的值,所以不能通过范围for增加容器对象的元素,顺便复习一下,使用范围for循环,除了最内层,其余的范围for循环都需要使用引用类型
//范围for循环
vector<int> vec{1, 2, 3, 4, 5};
for (int it:vec)
cout << it << endl;
- 循环中需要注意的细节
do while,do中定义的变量,while不可以使用,因为作用域的限制,do和while是不同的作用域;while和for定义的变量可以被块使用
函数
一、函数基础
1.局部静态变量
程序终止才被销毁,在此期间即使对象所在函数结束执行也不会对他有影响
#include <iostream>
using namespace std;
void fun()
{
static int x = 0;
x++;
cout << x << endl;
}
int main()
{
fun();//1
fun();//2
fun();//3
}
2.参数和返回类型
形参处理不同数量的实参
- initializer_list,也是一个容器,在vector中讲过{ }初始化,实现原理就是内置的initializer_list,基本用法如下
void fun(initializer_list<int> vec)
{
for (int it:vec)
cout << it << endl;
}
int main()
{
fun({1, 2, 3, 4, 5});
}
- 省略符形参…
作用:可以暂停编译器对类型的检查
缺点:不太安全
int sum(int count, ...)
{ //格式:count代表参数个数, ...代表n个参数
va_list ap; //声明一个va_list变量
va_start(ap, count); //第二个参数表示形参的个数
int sum = 0;
for (int i = 0; i < count; i++)
{
sum += va_arg(ap, int); //第二个参数表示形参类型
}
va_end(ap); //用于清理
return sum;
}
3.constexpr函数
- 定义:函数的返回类型及所有形参类型都是字面值类型(算术类型,引用,指针),而且函数体中必须有且仅有一条return语句
- 作用:constexpr的声明就等于告诉编译器这是在编译期就可以确定的值,你可以大胆的去优化(constexpr水挺深的,建议有兴趣可以研究,这里不过多赘述)
constexpr int fun(string &str)
{
str = "";//正确
str == "";//错误
return 0;
}
原因就在于str的值是在运行期间确定的,而==需要知道str的值,但=不需要,可以在编译期完成,所以=是正确的,==是错误的
- 与const的区别:const并不能代表“常量”,它仅仅是对变量的一个修饰,告诉编译器这个变量只能被初始化,且不能被直接修改,同时const不一定在编译期确定,也可以在运行期确定;constexpr则是相当于告诉编译器自己可以在编译期得出常量值(但有时会被欺骗,详见来源)
//const运行时确定值
int main()
{
int x;
cin >> x;
const int y = x;
}
二、函数重载
1.定义
- 函数名相同,形参数量或形参类型不同(两个函数,它们的形参列表一样但是返回类型不同,则第二个函数声明错误)
int fun(int x)
{
return 1;
}
bool fun(int x)//错误
{
return false;
}
- 不同作用域中无法重载函数名,class A和class B中的同名fun函数不算重载
2.const形参
- 拥有顶层const形参无法和另一个没有顶层const的形参区分开来,所以两个都出现算是重复声明,底层const不算(原理:对于重载,编译器需要根据传入的参数判断调用哪一个重载,所以需要针对实参有所区别,而非形参)
int fun(int *const x)
{
return 1;
}
int fun(int *x)//顶层const无法通过实参匹配
{
return 2;
}
3.匹配
- 精准匹配
- const类型转换:判别方式是判断实参是否是常量来决定选择哪个函数。指针类型的形参判别也是类似的(底层const)
- 类型提升:不是完全相同的类型,都会提升类型,char会提升为int
- 算术类型转换:该转换的级别都相同,即3.14是double,转换成long或者float都是同样等级,会产生二义性(算术类型转换的级别都一样,但好像char到int的级别比char到short的级别高,是我理解有问题还是C++ Primer讲错了???希望大佬帮忙解答一下)
int fun(long x)
{
return 1;
}
int fun(float x)
{
return 2;
}
int main()
{
double x = 3.14;
cout << fun(x) << endl;//二义性
}
- 类类型转换,涉及多态
类
一、认识类
1.细节
- 定义在类内部的函数是隐式的inline函数
- 参数列表后的const:修改隐式this指针的类型,将其定义为const T* const this。常量对象,以及常量对象的引用或指针都只能调用常量成员函数
2.静态成员
- 静态成员属于类,而非对象
- 不与任何对象绑定,不包含this指针,所以不能在static函数体内使用this指针
- 静态成员函数不能声明成const,因为const是针对对象的,但静态成员属于类
3.抑制构造函数定义的隐式转化
explicit——构造函数需要显示声明
- explicit,初始化时该关键词无影响
- explicit只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转化
class A
{
public:
A(int x, int y = 0)//把=0去掉,就会报错,因为多个实参的构造函数不能用于执行隐式转化
{
}
};
int main()
{
A tmp = 1;
}
- explicit只允许出现在类内的构造函数声明或类型转换函数处
- 用于显示声明构造函数,同时也抑制拷贝构造函数(因为拷贝构造函数在返回时会构造中间对象,这里会用到隐式的构造函数)
二、类的特性
1.可变数据成员
- mutable类型的数据可在const函数内部修改它的值
class A
{
private:
mutable int x, y;
public:
void fun() const
{
x = 3;//正确
y = 4;//正确
}
};
2.类定义处理
- 首先编译成员的声明,直到类全部可见是再编译函数体(所以函数定义可以写在外面)
3.类封装的优点
- 封装实现了类的接口和实现的分离,隐藏了类的实现细节,用户只能接触到类的接口(后续还会设计模式的总结,面向对象真的很强大!!!)
三、友元
1.特点
- 只能出现在类定义内部,但类内出现的具体位置不限
- 友元不是类的成员,不受区域访问控制级别的约束
- 在友元之外再专门对函数进行一次声明
总结
以上便是C++基础总结的上篇,只是看完《C++ Primer》以后的一点小总结,内容很简陋,主要是我自己在看书过程中遇到有疑问的一些点,然后经过查阅资料以后有所收获的,在这里和大家分享一下。如果这些知识点有任何问题,欢迎大家在评论区指出,我会虚心接受…