嵌入式Qt开发C++基础编程
文章目录
1、指针
1.指针的定义
在C++中,指针是一个变量,其值为另一个变量的地址。通过使用指针,你可以直接访问和操作内存中的特定位置。
以下是C++中指针的基本定义方式:
// 定义一个整数类型的指针变量p
int* p;
// 或者在定义的同时初始化
int x = 10;
int* ptr = &x; // ptr现在指向x的地址
在上面的代码中:
int* p;
定义了一个名为p
的指针变量,它可以存储一个int
类型变量的地址。int* ptr = &x;
定义了一个名为ptr
的指针变量,并立即将其初始化为x
的地址(使用&
运算符获取x
的地址)。
一旦你有了指向某个变量的指针,你就可以使用该指针来访问或修改该变量的值。例如:
*ptr = 20; // 使用*运算符来访问ptr指向的值,并将其设置为20
std::cout << x; // 输出: 20,因为ptr指向x,我们改变了x的值
需要注意的是,指针操作应该非常小心,因为不正确的指针操作可能导致程序崩溃、数据损坏或其他未定义的行为。确保在使用指针之前已经正确地初始化了它,并且在不再需要它时将其设置为 nullptr
或将其删除(如果是指向动态分配的内存)。
2.指针的运算
指针变量存放的是变量的地址,对变量进行*(解引用)运算,可以获得该地址下变量的值。
#include "iostream"
using namespace std;
//指针变量存放的是变量的地址,对变量进行*运算,可以获得该地址下变量的值
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
char str[10] = {'a','b','c','d','e','f','g','h','i','j'};
int *p = arr;
char *q = str;
for(int i = 0;i < 10;i++){
cout << *p << " " << *q << endl;
p++;
q++;
}
return 0;
}
3.结构体指针
在C++中,结构体(通常被称为类或结构体,但在C++中更常使用“类”这一术语)是一种复合数据类型,它允许你将多个不同类型的变量组合成一个单一的类型。然而,为了与C语言保持一致,C++也支持传统的struct
关键字来定义结构体。
当你使用结构体指针时,你实际上是在指向一个结构体类型的内存地址。这允许你通过指针来访问和修改结构体的成员。
以下是一个使用C++结构体指针的简单示例:
#include <iostream>
// 定义一个结构体
struct Student {
std::string name;
int age;
float gpa;
};
int main() {
// 在堆上分配一个Student结构体的实例
Student* ptr = new Student;
// 通过指针访问和修改结构体的成员
ptr->name = "Alice";
ptr->age = 20;
ptr->gpa = 3.5f;
// 打印结构体的内容
std::cout << "Name: " << ptr->name << std::endl;
std::cout << "Age: " << ptr->age << std::endl;
std::cout << "GPA: " << ptr->gpa << std::endl;
// 释放分配的内存
delete ptr;
return 0;
}
在这个示例中,我们首先定义了一个名为Student
的结构体,它包含三个成员:一个字符串name
,一个整数age
和一个浮点数gpa
。然后,在main
函数中,我们使用new
操作符在堆上分配了一个Student
结构体的实例,并将返回的指针存储在ptr
中。接下来,我们通过ptr
来访问和修改结构体的成员。最后,我们使用delete
操作符来释放分配的内存。
在访问结构体指针的时要使用->
操作。
4.结构体指针数组
在C++中,可以创建一个结构体指针数组,这意味着数组的每个元素都是一个指向结构体的指针。这种数据结构在需要存储多个结构体实例,但可能不希望在创建时立即分配所有内存的情况下特别有用。
以下是一个使用C++结构体指针数组的示例:
#include <iostream>
#include <string>
// 定义一个结构体
struct Student {
std::string name;
int age;
float gpa;
};
int main() {
// 创建一个包含5个Student指针的数组
Student* students[5];
// 为数组中的每个元素分配内存并初始化
for (int i = 0; i < 5; ++i) {
students[i] = new Student;
// 假设我们有一些数据
students[i]->name = "Student " + std::to_string(i + 1);
students[i]->age = 20 + i;
students[i]->gpa = 3.0f + (i % 3) * 0.5f; // 为了使GPA不同
}
// 遍历数组并打印每个学生的信息
for (int i = 0; i < 5; ++i) {
std::cout << "Name: " << students[i]->name << std::endl;
std::cout << "Age: " << students[i]->age << std::endl;
std::cout << "GPA: " << students[i]->gpa << std::endl;
std::cout << "-----" << std::endl;
}
// 释放每个结构体实例占用的内存
for (int i = 0; i < 5; ++i) {
delete students[i];
}
return 0;
}
在这个示例中,我们首先定义了一个结构体Student
。在main
函数中,我们创建了一个名为students
的数组,该数组包含5个指向Student
的指针。然后,我们使用一个循环为数组中的每个指针分配内存,并初始化每个结构体的成员。接下来,我们遍历数组并打印每个学生的信息。最后,我们使用另一个循环来释放每个结构体实例占用的内存。
注意,由于我们使用了new
来分配内存,因此必须使用delete
来释放它,以防止内存泄漏。在这个例子中,我们在创建和销毁每个Student
实例时都使用了显式的内存管理。然而,在更复杂的应用程序中,使用智能指针(如std::unique_ptr
或std::shared_ptr
)可能是一个更好的选择,因为它们可以自动管理内存,并减少因忘记释放内存而导致的错误。
#include "iostream"
using namespace std;
struct Student{
string name;
int age{};
int score{};
};
int main(){
struct Student stu[3] = { {"张三", 18, 100},
{"李四", 19, 99},
{"王五", 20, 98}};
struct Student *p = stu;
// for (int i = 0; i < 3; ++i) {
// cout << p->name << " " << p->age << " " << p->score << endl;
// p++;
// }
for (int i = 0; i < 3; ++i) {
cout << p[i].name << " " << p[i].age << " " << p[i].score << endl;
}
auto *p2 = new struct Student[3] { {"张三", 18, 100},
{"李四", 19, 99},
{"王五", 20, 98}};
for (int i = 0; i < 3; ++i) {
cout << p2[i].name << " " << p2[i].age << " " << p2[i].score << endl;
}
delete []p2;
return 0;
}
在这个示例中,定义了一个指向结构体数组的指针,此时访问时,p[i]
中的元素是结构体,故需要使用.
操作。
5.数组元素的插入
数组元素的插入实质上是将原始数组的元素以及需要添加的元素传入新的数组。
//
// Created by 86189 on 2024/5/24.
//
#include "iostream"
using namespace std;
int main(){
int *arr = new int[10] {1,2,3,4,5,6,7,8,9,10};
int *NewArr = new int[12];
int offset = 0;
for (int i = 0; i < 12; i++) {
if (i == 1)
{
offset++;
NewArr[i] = 2;
continue;
}
else if (i == 4)
{
offset++;
NewArr[i] = 5;
continue;
}
NewArr[i] = arr[i-offset];
}
for (int i = 0; i < 10; ++i) {
cout << arr[i] << " ";
}
cout << endl;
for (int i = 0; i < 12; ++i) {
cout << NewArr[i] << " ";
}
delete []arr , delete []NewArr;
return 0;
}
实例中定义了中间值offset
来表示数组元素插入过程中插入元素的个数,例如示例中在1位置和4位置插入,则原始数组下标为1的元素则需要填入新数组下标为2的位置。
5.数组元素的删除
移除数组元素本质上仍然是将原始数组减少元素后,传入新的数组。
#include "iostream"
using namespace std;
int main(){
//定义一个数组,并赋值
int *arr = new int [5] {1,2,3,4,5};
int *NewArr = new int [3];
int offset = 0;
for(int i = 0; i < 5; i++){
if(i == 0 || i == 3){
offset++;
continue;
}
NewArr[i-offset] = arr[i];
}
for(int i = 0; i < 3; i++){
cout << NewArr[i] << " ";
}
delete [] arr , delete [] NewArr;
return 0;
}
4.常量指针
const
用来修饰常量指针。
#include "iostream"
using namespace std;
int main(){
int num1 = 10 , num2 = 20;
//指向常量的指针 存储值不可变 指向可变
const int *p = &num1;
//*P = 100; 不可更改值
p = &num2;// 可以更改指针指向
cout << *p << endl;
//常量指针
int * const p1 = &num1;
*p1 = 100; // 可以更改值
// p1 = &num2; 不可更改指针指向
cout << *p1 << endl;
//指向常量的常量指针
const int * const p2 = &num1;
//*P2 = 100;值不可变
//p2 = &num2; 指针指向不可变
cout << *p2 << endl;
return 0;
}
5.new运算符
new运算符用来开辟内存空间。但需要自己手动删除。
例如:开辟数组内存空间。
int *arr = new int [5] {1,2,3,4,5};
int *NewArr = new int [3];
delete [] arr , delete [] NewArr;
开辟指针空间。
Student* ptr = new Student;
delete ptr;
2、引用
引用不建议作为函数返回值。
引用可以看作为变量取得别名,操作引用变量等同于操作原始变量,由于引用的这种特性,则引用在定义时必须赋值,即初始化。
#include "iostream"
using namespace std;
//编写应用引用的示例代码
int main(){
int a = 10;
int &b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
b = 20; //改变引用的值,也会改变原始变量a的值
cout << "a = " << a << endl;
cout << "b = " << b << endl;
return 0;
}
3、函数
当涉及到C++的基础函数知识时,以下是一些核心概念和示例:
1. 函数定义和调用
定义:
// 定义一个函数,计算两个整数的和
int add(int a, int b) {
return a + b;
}
// 主函数
int main() {
// 调用函数
int sum = add(5, 3);
std::cout << "Sum: " << sum << std::endl;
return 0;
}
2. 函数参数
示例:带有参数的函数
// 定义一个函数,计算两个数的乘积
int multiply(int x, int y) {
return x * y;
}
// 主函数
int main() {
// 调用函数,传递参数
int result = multiply(7, 2);
std::cout << "Result: " << result << std::endl;
return 0;
}
3. 函数的返回值
示例:返回值的函数
// 定义一个函数,判断一个数是否为偶数
bool isEven(int num) {
return num % 2 == 0;
}
// 主函数
int main() {
// 调用函数,并使用返回值
bool isEvenNumber = isEven(10);
if (isEvenNumber) {
std::cout << "10 is even." << std::endl;
} else {
std::cout << "10 is not even." << std::endl;
}
return 0;
}
4. 函数指针
示例:使用函数指针
// 定义两个函数
void func1() {
std::cout << "Function 1 called." << std::endl;
}
void func2() {
std::cout << "Function 2 called." << std::endl;
}
// 主函数
int main() {
// 定义函数指针
void (*funcPtr)();
// 分配函数给指针
funcPtr = func1;
funcPtr(); // 输出 "Function 1 called."
funcPtr = func2;
funcPtr(); // 输出 "Function 2 called."
return 0;
}
5.函数传参
1. 值传递(Pass by Value)
值传递形参无法修饰实参,在值传递中,函数接收的是参数值的副本。这意味着在函数内部对参数所做的任何修改都不会影响到传递给函数的原始变量。
示例:
#include <iostream>
void modifyValue(int num) {
num = num + 10; // 修改的是num的副本,原始变量不受影响
std::cout << "Inside function: " << num << std::endl;
}
int main() {
int x = 5;
std::cout << "Before function call: " << x << std::endl;
modifyValue(x); // 传递x的值给函数
std::cout << "After function call: " << x << std::endl; // x的值仍然是5
return 0;
}
2. 地址传递(Pass by Reference)
在地址传递中,函数接收的是参数的地址(引用),而不是参数值的副本。因此,在函数内部对参数所做的任何修改都会影响到传递给函数的原始变量。
示例:
#include <iostream>
void modifyReference(int& num) { // 使用&表示引用传递void mod(int * p)
num = num + 10; // 修改的是原始变量
std::cout << "Inside function: " << num << std::endl;
}
int main() {
int x = 5;
std::cout << "Before function call: " << x << std::endl;
modifyReference(x); // 传递x的引用给函数modi(&x)
std::cout << "After function call: " << x << std::endl; // x的值现在是15
return 0;
}
注意事项:
- 引用传递通常用于大型对象或需要修改原始数据的场景,因为它可以避免复制对象,从而提高效率。
- 引用传递时,必须确保传递给函数的引用是有效的,并且引用在函数执行期间不会失效(例如,引用的对象没有被销毁)。
- 引用传递也可以用于常量引用(
const int&
),这样可以在不修改原始数据的情况下传递数据。
常量引用示例:
#include <iostream>
void printValue(const int& num) { // 使用const表示引用不会修改数据
std::cout << "Inside function: " << num << std::endl;
}
int main() {
int x = 5;
printValue(x); // 传递x的常量引用给函数
return 0;
}
在这个例子中,printValue
函数接收一个常量引用,这意味着它不能修改num
的值。这增加了代码的安全性,因为它防止了函数意外地修改原始数据。