1. 输出数据 std::cout
std::cout 向控制台输出数据
<< 可以拼接多个数据 std::cout<<"name:"<<"jack"<<"age:"<<17<<"\n";
std::endl 用于换行 std::cout<<"name:"<<std::endl;
using namespace std; 指定缺省的命名空间
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World!\n"; //std::cout 控制台输出内容的指令 <<输出的运算符
std::cout << "name:" << "jack" << " age:" << 17 << std::endl;
}
2. 常量使用
1)宏常量
#define PI 3.14159
2)const 修饰的常量
const int days=7;
3. C++标识符的命名
- 不能数字开头
- 严格区分大小写
- 只能数字、字母、下划线
- 不能用C++关键字作为名称(关键字也叫保留字,C++预先保留的标识符,用于声明类型、对象、函数、命名空间等,有60多个)
- 望文知义
4. 输入数据
1)控制台界面输入 std::cin>>变量名;
2)文件中读取
3)数据库读取
4)网络中读取
5. 算数运算符 、逻辑运算符
算数运算符: + - * / %
- 整数进行除法运算时,如果分子为0,程序将异常退出
- 浮点数进行除法运算时,如果分子为0.0,将得到 inf ( infinite,无穷大,浮点数没有绝对的0,是无穷小的接近于0)
- 两个整数进行除法运算时,将舍去小数部分,得到一个整数
- 整数与浮点数进行除法运算时,得到的结果是浮点数
- 在整数前面加(float)或(double)可以将整数转换为float或 double类型,float或 double类型转为整数则舍弃小数部分
- 取模运算只能用于整数(分子也不能为0)
逻辑运算符:&& || !
6. C++11赋值
//C++标准
int a=(15);
int a(15);
//C++11标准目的是为了提高开发效率,Linux下编译需要加上 -std=c++11
int a={15};
int a{15};
7. goto语句
容易出问题
int main()
{
goto bbb;
std::cout << "hello";
bbb:
std::cout << "hello";
}
8. 转义字符和原始字面量
使用转义字符的原因
- 控制字符没有符号,无法书写,只能用其他符号代替
比如字符串中包含了\n
,就输出换行,\n
就是转义字符
经常用到的转义字符有4个\0 空 \n 换行 \r 回车移到本行开头 \t 跳到下一个TAB,作用是对齐
- 某些符号被C++征用,语义冲突 ,只能用其他符号代替
\\斜线 \" 双引号 \' 单引号
原始字面量可以直接表示字符串的实际含义,不需要转义和连接(字符串换行需要+ )
语法R"(字符串内容)"
string path1 = "C:\\Users\\Admin\\Desktop\\c\\ConsoleApplication1";
string path2 = R"abcd(C:\Users\Admin\Desktop\c\ConsoleApplication1)abcd"; //加标签增加可读性
9. 字符串型
C++风格字符串:string 变量名=“字符串内容” 本质是类,封装了C 风格字符串
C 风格字符串:char 变量名[]=“字符串内容” 本质是字符数组
10. 起别名
1)为复杂的类型创建别名,方便记忆书写
2)创建与平台无关的数据类型,提高程序兼容性
typedef long long llong;
//不同系统中相同类型数据所占字节大小可能不同,为移植带来了一点麻烦
typedef short int16_t;
typedef int int32_t;
typedef long int64_t;
11. C++的内存模型
内存分为内核空间和用户空间
使用堆区内存步骤:
new 数据类型(初始值)
delete(地址值)
int *P=new int(5);
std::cout<<*P; //5
delete P;
12. 二级指针
int i=8;
int *pi =&i;
int **ppi=π
int main()
{
int i = 8;
int* pi = &i;
int** ppi = π
std::cout << "&i :"<<&i<< endl;
std::cout << "pi :" << pi << endl;
std::cout << "&pi :" << &pi << endl;
std::cout << "*ppi :" << *ppi << endl;
std::cout << "**ppi :" << **ppi << endl;
}
//&i :0000002031F7F644
//pi :0000002031F7F644
//&pi :0000002031F7F668
//*ppi :0000002031F7F644
//**ppi :8
使用指针有两个目的: 1)传递地址; 2)存放动态分配的内存的地址。
在函数中,如果传递普通变量的地址,形参用指针; 传递指针的地址,形参用二级指针。
把普通变量的地址传入函数后可以在函数中修改变量的值;
把指针的地址传入函数后可以在函数中指修改针的值。
13. 函数指针和回调函数
函数指针声明函数返回值 (*函数指针名)(形参类型1,形参类型2)
语法:函数指针名=函数名
调用:函数指针名(参数) // (*函数指针名)(参数)c语言
//using namespace std;
void func(std::string name)
{
std::cout << "hello," << name;
}
int main()
{
void (*pfunc)(std::string);
pfunc = func;
pfunc("jack");
}
回调函数是把函数功能嵌入到了另一个函数中,该函数提供了主体的流程和框架,具体的功能可以由回调函数实现
void f(void(*pf)())
{
cout << "start\n";
pf();
cout << "end\n";
}
void f1()
{
cout << "jack\n";
}
void f2()
{
cout << "lucy\n";
}
int main()
{
f(f1);
f(f2);
}
传入参数有两种方式:修改回调函数或者修改调用函数
修改回调函数
void f1(int a)
{
cout << "jack\n";
cout << a;
}
void f2(int a)
{
cout << "lucy\n";
cout << a;
}
void f(void(*pf)(int))
{
cout << "start\n";
int b=3;
pf(b);
cout << "end\n";
}
int main()
{
f(f1);
f(f2);
}
修改调用函数,实参从外面传入
void f1(int a)
{
cout << "jack\n";
cout << a;
}
void f2(int a)
{
cout << "lucy\n";
cout << a;
}
void f(void(*pf)(int))
{
cout << "start\n";
//int b=3;
pf(b);
cout << "end\n";
}
int main()
{
f(f1,3);
f(f2,4);
}
回调函数在多线程和网络通讯中常用
引用
引用是引用变量简称,是C++新增的复合类型;引用时已定义的变量的别名,作用是用作函数的形参和返回值
创建引用的语法:数据类型&引用名=原变量名;
注意
- 引用的数据类型与原变量类型一样
- 引用名和原变量名一样,他们的值和内存单元都一样。和原变量一个东西
- 引用必须在声明时初始化,之后不可改变
引用是指针常量(数据类型*const 变量名,指向的变量/对象 不可改变)的伪装
int a=3;
int&ra=a;
int*const rb=&a;
作为函数的参数
把函数的形参声明为引用,调用函数的时候,形参将成为实参的别名。这种方法也叫按引用传递
或传引用
。(传值、传地址、传引用只是说法不同,其实都是传值。)引用的本质是指针,传递的是变量的地址,在函数中,修改形参会影响实参
。
1)传引用的代码更简洁(代码写法和传值一样,唯一的改动是把函数的形参声明为引用)
//传值
void func1(int no,string str)
{
no=8;
str="i have a bird";
}
//传地址
void func2(int* no,string* str)
{
*no=8;
*str="i have a bird";
}
//引用
void func3(int& no,string& str)
{
no=8;
str="i have a bird";
}
int main()
{
int bh=3;
string message="i have no birds";
//func1(bh,message);
//func2(&bh,&message);
func3(bh,message);
}
2)传引用不必使用二级指针
void func1(int** p) //形参是二级指针
{
*p = new int(3);
}
void func2(int*& p) //形参是一级指针的别名
{
p = new int(3);
}
int main()
{
int* p=nullptr; //存放子函数中动态分配的内存
func1(&p); //传地址,实参填指针p的地址 &p=*p !
func2(p); //传引用,实参填指针p
}
3)引用的属性和特别之处
如果引用的数据对象类型不匹配,当引用为 const 时,C++将创建临时变量,让引用指向临时变
什么时候将创建临时变量呢? ------------引用是 const / 数据对象的类型不正确,但可以转换为正确的类型
//引用
void func3(const int& no, const string& str)
{
no=8;
str="i have a bird";
}
int main()
{
//func1(bh,message);
//func2(&bh,&message);
func3(3,"i have no birds");
}
函数的返回值
传统的函数返回机制与值传递类似,函数的返回值被拷贝到一个临时位置(寄存器或栈),然后调用程序再使用这个值。
这样存在一个问题:如果返回一个结构体,就将整个结构体拷贝到临时位置
如果返回地址,用一个指针接收他的值,就不会产生内存的拷贝
如果返回引用,也不会拷贝内存
语法:
返回值的数据类型& 函数名(形参列表)
注意:
- 如果返回局部变量的引用,其本质是野指针
- 可以返回函数的引用形参、类的成员、全局变量、静态变量。
- 返回引用的函数是被引用的变量的别名,将const 用于引用的返回类型
int &func()
{
static int ii=3;
return ii;
}
int func1(int& ra)
{
ra++;
return ra;
}
int main()
{
int&b=func();
cout<<b<<endl;
int a=3;
int&b=func1(a); //ra 是a的别名,rb是ra的别名,所以ra rb都是a的别名,地址、值都相同
cout<<b<<endl;
}
形参的使用场景
传值、传地址和传引用的指导原则《C++Primer Plus》
如果不需要在函数中修改实参:
- 如果实参很小,如内置数据类型或小型结构体,则按值传递
- 如果实参是数组,则使用 const 指针,因为这是唯一的选择(没有为数组建立引用的说法)
- 如果实参是较大的结构,则使用 const 指针或 const引用
- 数据实参是类,则使用 const引用,传递类的标准方式是按引用传递
2)如果需要在函数中修改实参
- 如果实参是内置数据类型,则使用指针。只要看到 func(&x)的调用,表示函数将修改 x
- 如果实参是数组,则只能使用指针
- 如果实参是结构体,则使用指针或引用
- 如果实参是类,则使用引用。
14. 结构体
struct 结构体名
{
数据类型 成员1;
数据类型 成员2;
数据类型 成员3;
};
访问成员 . ->
struct 结构体名 变量名={0};struct 结构体名 变量名={成员1值,......};
结构体名 变量名; // C++可以不写关键字
- C++结构体可以有函数,但不提倡(C++的结构体就是类)
- C++11,定义结构体可以指定初始值
- C++可以在定义结构体的时候定义结构体变量,还可以初始化
struct 结构体名
{
数据类型 成员1;
数据类型 成员2;
数据类型 成员3;
}structVar={0};
占用内存大小-结构体内存对齐
15. 结构体指针
struct st_student
{
char name[20];
int age;
char sex;
};
void func2(const st_student* p)
{
cout << "name:" << p->name;
}
int main()
{
st_student stStudent = { "jack",18,'X'};
cout << "name:" << stStudent.name << " age:" << stStudent.age << " Sex:" << stStudent.sex << endl;
st_student* p = &stStudent;
cout << "name:" << (*p).name << " age:" << (*p).age << " Sex:" << (*p).sex << endl;
cout << "name:" << p->name << " age:" << p->age << " Sex:" << p->sex << endl;
func2(&stStudent); //作为参数传递
}
结构体变量名没有特殊的地方,不是地址
作用
1)函数的参数
2)动态分配内存
函数重载(函数多态)
C++运行定义相同名称的函数,条件是特征不同(形参个数,数据类型,排列顺序)
在实际开发中,视需求重载各种数据类型,不要重载功能不同的函数(可读性差)
-
使用重载函数时,如果数据类型不匹配,C++尝试使用类型转换与形参进行匹配,如果转换后有多个函数能匹配上,编译将报错
-
引用可以作为函数重载的条件,但是,调用重载函数的时候,如果实参是变量,编译器形参类型的本身和类型引用视为同一特征
-
如果重载函数有默认参数,调用函数时,可能导致匹配失败(本来有3个参数,第3个参数有个默认值,现在调用是只传入了两个参数,两个函数都能匹配,调用不明确)
-
const不能作为函数重载的特征。(func(int a)和func(const int a)两者不能重载)
-
返回值数据类型不同不能作为重载的特征
类
结构体成员一多,赋值、操作这些代码就非常多,臃肿。在结构体中增加了成员函数,成员函数的特点就是可以直接访问结构体的成员变量,其他和普通的函数没区别
在C++中,结构体就是类,把定义结构体的关键字struct 改为 class。C语言是把代码打包到函数中,C++可以打包到类中。
对于面向编程而言,一切都是对象,对象用类来描述。类把对象的数据和操作数据的方法作为一个整体考虑。
- 类的成员可以是变量,也可以是函数。
- 类的成员变量也叫属性
- 类的成员函数也叫方法/行为,类的成员函数(太长了)可以定义在类的外面,类中只写声明
- 用类定义一个类的变量叫做创建(或实例化)一个对象
- 类的成员变量和成员函数的作用域和生命周期与对象的作用域和生命周期相同
类的权限
- 类的成员有三种访问权限: public、private和protected,分别表示公有的、私有的和受保护的
- 在类的内部(类的成员函数中),无论成员被声明为 public还是private,都是可以访问
- 在类的外部(定义类的代码之外),只能访问public成员,不能访问private、protected成员
- 在一个类体的定义中,private和public可以出现多次。结构体的成员缺省为public,类的成员缺省为private
- private的意义在于隐藏类的数据和实现,把需要向外暴露的成员声明为public
class Student
{
public:
int age;
string name;
private:
char sex;
public:
void setValue(int age, string name, char sex);
void show()
{
cout << "name:" << name << " age:" << age <<" Sex:" <<sex<< endl;
}
};
void Student::setValue(int age, string name, char sex)
{
this->age = age;
this->name = name;
this->sex = sex;
show();
}
int main()
{
st_student stStudent = { "jack",18,'X'};
cout << "name:" << stStudent.name << " age:" << stStudent.age << " Sex:" << stStudent.sex << endl;
st_student* p = &stStudent;
cout << "name:" << (*p).name << " age:" << (*p).age << " Sex:" << (*p).sex << endl;
cout << "name:" << p->name << " age:" << p->age << " Sex:" << p->sex << endl;
func2(&stStudent);
Student stu;
stu.setValue(17, "jack", 'X');
}
- 在类的外部,一般不直接访问(读和写)对象的成员,而是用成员函数。<
- 对象一般不用memset()清空成员变量,可以写一个专用于清空成员变量的成员函数
- 用结构体描述纯粹的数据,用类描述对象
- 在类的声明中定义的函数都将自动成为内联函数;在类的声明之外定义的函数如果使用了inline限定符,也是内联函数(提高效率,但是占用更多的内存)。
- 为了区分类的成员变量和成员函数的形参,把成员变量名加m_前缀或_后缀,如m_name或name_。c
构造函数和析构函数
构造函数:创建对象时,自动初始化
析构函数:销毁对象前,自动清理
构造函数
函数名=类名,无返回值不写void,权限public,可以有参数,可以有多种不同参数的构造函数。只能自动调用一次,不能手动
析构函数
函数名=~类名,无返回值不写void,权限public,无参数。只能自动调用一次,也可以手动(非常少使用)
1)如果没有提供构造/析构函数,编译器将提供空实现的构造/析构函数。
2)如果提供了构造/析构函数,编译器将不提供空实现的构造/析构函数。
3)创建对象的时候,如果重载了构造函数,编译器根据实参匹配相应的构造函数。没有参数的构造函数也叫默认构造函数。
4)创建对象的时候不要在对象名后面加空的圆括号,编译器误认为是声明函数。(如果没有构造函数、构造函数没有参数、构造函数的参数都有默认参数)
5)在构诰函数名后面加括号和参数不是调用构造函数,是创建匿名对象(临时对象)。
6)以下两行代码有本质的区别:
CGirl girl = CGirl("西施",20);//显示创建对象。
pCGirl girl; //创建对象。'
girl = CGirl("西施",20); //创建匿名对象,然后给现有的对象赋值。
7)用new/delete创建/销毁对象时,也会调用构造/析构函数。
CGirl *p=new CGirl;
CGirl *p=new CGirl();
CGirl *p=new CGirl("西施",20);
8)不建议在构造/析构函数中写太多的代码,可以调用成员函数(太复杂写在成员函数中,然后构造函数再调用成员函数)。
拷贝构造函数
拷贝构造函数的使用场景
-
用一个已存在的对象创建新的对象,不会调用(普通)构造函数,而是调用拷贝构造函数。
如果类中没有定义拷贝构造函数,编译器将提供一个拷贝构造函数,它的功能是把已存在对象的成员变量赋值给新对象的成员变量。用一个已存在的对象创建新的对象语法:
类名 新对象名(已存在的对象名)
类名 新对象名=已存在的对象名;
如何定义拷贝构造函数:
类名(const 类名&对象名){} -
以值传递的方式调用函数时,如果实参为对象,会调用拷贝构造函数。
void func(CGgirl g)
{
g.show();
}
int main()
{
CGgirl g1;
func(g1);//传递的实参是值,且是对象
}
- 函数以值的方式返回对象时,可能会调用(VS会,Linux不会,g++编译器做了优化)